#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstdlib>
#include <fstream>
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>

#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif
using namespace cv;
using namespace std;


cl_program load_program(cl_context context, const char* filename)
{
	std::ifstream in(filename, std::ios_base::binary);
	if (!in.good()) {
		return 0;
	}

	// get file length
	in.seekg(0, std::ios_base::end);
	size_t length = in.tellg();
	in.seekg(0, std::ios_base::beg);

	// read program source
	std::vector<char> data(length + 1);
	in.read(&data[0], length);
	data[length] = 0;

	// create and build program 
	const char* source = &data[0];
	cl_program program = clCreateProgramWithSource(context, 1, &source, 0, 0);
	if (program == 0) {
		return 0;
	}

	if (clBuildProgram(program, 0, 0, 0, 0, 0) != CL_SUCCESS) {
		return 0;
	}

	return program;
}

/////
cl_command_queue queue;
cl_context context;
cl_int err;
cl_uint num;
/////
int detecDEVPLA(){

	err = clGetPlatformIDs(0, 0, &num);
	if (err != CL_SUCCESS) {
		std::cerr << "Unable to get platforms\n";
		return 0;
	}

	std::vector<cl_platform_id> platforms(num);
	err = clGetPlatformIDs(num, &platforms[0], &num);
	if (err != CL_SUCCESS) {
		std::cerr << "Unable to get platform ID\n";
		return 0;
	}
	cl_context_properties prop[] = { CL_CONTEXT_PLATFORM, reinterpret_cast<cl_context_properties>(platforms[0]), 0 };
	context = clCreateContextFromType(prop, CL_DEVICE_TYPE_DEFAULT, NULL, NULL, NULL);
	if (context == 0) {
		std::cerr << "Can't create OpenCL context\n";
		return 0;
	}



	size_t cb;
	clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &cb);
	std::vector<cl_device_id> devices(cb / sizeof(cl_device_id));
	clGetContextInfo(context, CL_CONTEXT_DEVICES, cb, &devices[0], 0);
	clGetDeviceInfo(devices[0], CL_DEVICE_NAME, 0, NULL, &cb);
	std::string devname;
	devname.resize(cb);
	clGetDeviceInfo(devices[0], CL_DEVICE_NAME, cb, &devname[0], 0);
	std::cout << "Device: " << devname.c_str() << "\n";

	queue = clCreateCommandQueue(context, devices[0], 0, 0);
	if (queue == 0) {
		std::cerr << "Can't create command queue\n";
		clReleaseContext(context);
		return 0;
	}

}

int main(int argc, char** argv){
	Mat image;



	detecDEVPLA();		//detec device and platform


	char img_name[50]="";

	strcpy(img_name,"8K.jpg");
	image = imread(img_name, CV_LOAD_IMAGE_COLOR);
	LARGE_INTEGER t1, t2, ts;
	QueryPerformanceFrequency(&ts);
	QueryPerformanceCounter(&t1);
 



	int DATA_SIZE = image.cols * image.rows*image.channels();



	uchar *data;
	data = image.ptr<uchar>(0);


	cl_mem cl_data = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(cl_uchar) * DATA_SIZE, image.ptr(0), NULL);
	cl_mem cl_res = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(cl_uchar) * DATA_SIZE, NULL, NULL);


	if (cl_data == 0 || cl_res == 0) {
		std::cerr << "Can't create OpenCL buffer\n";
		clReleaseMemObject(cl_data);
		clReleaseMemObject(cl_res);
		clReleaseCommandQueue(queue);
		clReleaseContext(context);
		system("pause");
		return 0;
	}

	cl_program program = load_program(context, "BGR2GRAY.cl");

	if (program == 0) {
		std::cerr << "Can't load or build program\n";
		clReleaseMemObject(cl_data);
		clReleaseMemObject(cl_res);
		clReleaseCommandQueue(queue);
		clReleaseContext(context);
		system("pause");
		return 0;
	}

	cl_kernel adder = clCreateKernel(program, "adder", 0);
	if (adder == 0) {
		std::cerr << "Can't load kernel\n";
		clReleaseProgram(program);
		clReleaseMemObject(cl_data);
		clReleaseMemObject(cl_res);
		clReleaseCommandQueue(queue);
		clReleaseContext(context);
		system("pause");
		return 0;
	}

	clSetKernelArg(adder, 0, sizeof(cl_mem), &cl_data);
	clSetKernelArg(adder, 1, sizeof(cl_mem), &cl_res);

	size_t work_item_size = DATA_SIZE / 3;
	err = clEnqueueNDRangeKernel(queue, adder, 1, 0, &work_item_size, 0, 0, 0, 0);


	Mat After_image = Mat::zeros(Size(image.cols, image.rows), CV_8UC3);
	uchar *GRAY;
	GRAY = After_image.ptr<uchar>(0);

	if (err == CL_SUCCESS) {
		err = clEnqueueReadBuffer(queue, cl_res, CL_TRUE, 0, sizeof(uchar) * DATA_SIZE, After_image.ptr(0), 0, 0, 0);
	}

	QueryPerformanceCounter(&t2);
	imshow("result", After_image);
	imwrite("GRAY_8K.jpg",After_image);

	ofstream time_result;
	time_result.open("GPU_result_time.txt",ofstream::out|ofstream::app);
	time_result<<"GPU processing "<<img_name<<" time   :  "<<((t2.QuadPart - t1.QuadPart) / (double)(ts.QuadPart))<<" sec "<<endl;
	time_result.close();


	return 0;
	}