#include "Common.h"
#include "OpenCLHandler.h"
#include <iostream>

void OpenCLHandler::printTimeStats(cl::Event& event)
{
	cl_int err = CL_SUCCESS;
	event.wait();
	cl_ulong execStart, execEnd;
	bool success = true;
	execStart = event.getProfilingInfo<CL_PROFILING_COMMAND_START>(&err);
	if (err != CL_SUCCESS)
	{
		//std::cerr << "Error during profile query: CL_PROFILING_COMMAND_START ["
			//<< err << "]." << std::endl;
		success = false;
	}
	execEnd = event.getProfilingInfo<CL_PROFILING_COMMAND_END>(&err);
	if (err != CL_SUCCESS)
	{
		//std::cerr << "Error during profile query: CL_PROFILING_COMMAND_END ["
			//<< err << "]." << std::endl;
		success = false;
	}
	//std::cout << "[start] " << execStart << " [end] " << execEnd
	// << " [time] " << (execEnd - execStart) / 1e+06 << "ms." << std::endl;
	if (success){
		std::cout << "GPU [time] " << (execEnd - execStart) / 1e+06 << " ms" <<
			std::endl;
	}
	else {
		std::cout << "GPU [time] Unavailable" << std::endl;
	}
	
}

OpenCLHandler::OpenCLHandler(std::string kernelcode)
{

	cl_int err = CL_SUCCESS;

	// Get a platform ID
	std::vector<cl::Platform> platforms;
	cl::Platform::get(&platforms);
	if (platforms.size() == 0)
	{
		std::cout << "Unable to find suitable platform." << std::endl;
		exit(-1);
	}

	std::cout << "Running on: " << platforms[0].getInfo<CL_PLATFORM_NAME>() << std::endl;

	// Create a context
	cl_context_properties properties[] =
	{ CL_CONTEXT_PLATFORM, (cl_context_properties)(platforms[0])(), 0 };
	context = cl::Context(CL_DEVICE_TYPE_GPU, properties);

	// Enumerate the devices
	std::vector<cl::Device> devices = context.getInfo<CL_CONTEXT_DEVICES>();
	std::cout << "Global memory: " << devices[0].getInfo<CL_DEVICE_GLOBAL_MEM_SIZE>() << std::endl;

	max_workgroup_size = devices[0].getInfo<CL_DEVICE_MAX_WORK_GROUP_SIZE>();
	std::cout << "Max workgroup: " << max_workgroup_size << std::endl << std::endl;

	// Create the command queue
	cl::Event event;
	queue = cl::CommandQueue(context, devices[0], CL_QUEUE_PROFILING_ENABLE, &err);
	// Create the OpenCL program
	//std::string programSource = FileToString("../kernels/programs.cl");
	std::string programSource = FileToString(kernelcode);
	program = cl::Program(context, programSource);
	err = program.build(devices);
	if (!CheckCLError(err))
	{
		for (size_t devID = 0; devID < devices.size(); ++devID)
		{
			std::cout << "Device: " << devID << std::endl;
			std::cout << "Build Status: " << program.getBuildInfo<CL_PROGRAM_BUILD_STATUS>(devices[devID]) << std::endl;
			std::cout << "Build Options:\t" << program.getBuildInfo<CL_PROGRAM_BUILD_OPTIONS>(devices[devID]) << std::endl;
			std::cout << "Build Log:\t " << program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(devices[devID]) << std::endl;
			std::cout << "--------------------------------------------------" << std::endl;
		}
		exit(-1);
	}
}

bool OpenCLHandler::run_test(TestCase* test)
{
	cl::Event gpuEvent;

	test->gpu_compute(&context, &queue, &program, &gpuEvent);

	Timer::measure([&]() {
		test->cpu_compute();
	}, 5);

	printTimeStats(gpuEvent);

	test->collect_results(&queue);

	return test->validate_results();
}

size_t OpenCLHandler::get_max_size()
{
	return max_workgroup_size;
}