#include <fstream>

#include "MonteCarloTests.h"
#include "Common.h"

LFG::LFG(size_t _randomNumbers, size_t _threadCount, size_t _randomStateSize)
{
	randomNumbers = _randomNumbers;
	threadCount = _threadCount;
	randomBuffer = std::vector<float>(threadCount * randomNumbers);
	seedBuffer = std::vector<float>(threadCount);
	kernel_name = "randomLFG";
	randomStateSize = _randomStateSize;

	for (int i = 0; i < threadCount; ++i)
	{
		seedBuffer[i] = rand();
	}
}

void LFG::collect_results(cl::CommandQueue* queue)
{
	queue->enqueueReadBuffer(clResultBuffer, true, 0, sizeof(float) * threadCount * randomNumbers, randomBuffer.data());
}

void LFG::gpu_compute(cl::Context* context, cl::CommandQueue* queue, cl::Program* program, cl::Event* Event)
{
	cl_int err = CL_SUCCESS;
	cl::Kernel kernel = cl::Kernel(*program, kernel_name.c_str(), &err);
	CheckCLError(err);

	clInputBuffer = cl::Buffer(*context, CL_MEM_READ_ONLY, sizeof(float) * threadCount, NULL, &err);
	queue->enqueueWriteBuffer(clInputBuffer, true, 0, sizeof(float) * threadCount, seedBuffer.data());

	clResultBuffer = cl::Buffer(*context, CL_MEM_WRITE_ONLY, sizeof(float) * threadCount * randomNumbers, NULL, &err);
	cl::Buffer randomStates(*context, CL_MEM_WRITE_ONLY, sizeof(float) * threadCount * randomStateSize, NULL, &err);

	// Set the kernel parameters	
	kernel.setArg(0, randomNumbers);
	kernel.setArg(1, clInputBuffer);
	kernel.setArg(2, randomStateSize);
	kernel.setArg(3, randomStates);
	kernel.setArg(4, clResultBuffer);

	// Enqueue the kernel: threadCount threads in total, each generating random numbers in [0,1] randomNumbers times
	queue->enqueueNDRangeKernel(kernel,
		cl::NullRange,
		cl::NDRange(threadCount, 1),
		cl::NullRange,
		NULL,
		Event);
}

void LFG::cpu_compute()
{
}

bool LFG::validate_results()
{
	std::ofstream ofs("randoms_" + description() + ".txt", std::ofstream::out);
	for (int i = 0; i < threadCount; ++i) {
		for (int j = 0; j < randomNumbers; ++j) {
			ofs << j << " " << randomBuffer[i + j * threadCount] << std::endl;
		}
	}
	ofs.close();
	return true;
}

std::string LFG::description()
{
	return kernel_name + "(numbers=" + std::to_string(randomNumbers) + ",threads=" + std::to_string(threadCount) + ",state="+std::to_string(randomStateSize) + ")";
}