#include <sycl/sycl.hpp>
#include <iostream>
#include <string>
#include <chrono>

#include "args.hpp"
#include "grid.hpp"
#include "serial.hpp"
#include "sycl.hpp"

// definition of real numbers type for the computations
#ifdef FP32
typedef float real_t;
#else
typedef double real_t;
#endif

void saveCoverage(std::string outputFile, const std::vector<std::size_t>& coverage)
{
	std::ofstream out(outputFile);
	if (!out.is_open()) {
		throw std::runtime_error("Unable to open output file '" + outputFile + "'.");
	}
	for (std::size_t i = 0; i < coverage.size(); ++i) {
		out << coverage[i] << std::endl;
	}
}


/**
 * Run selected algorithm on given grid and measure performance.
 */
template<class ALG>
void run(const Grid<real_t, 2> &points, real_t radius, std::string outputFile)
{
	ALG alg(points.size<1>());
	std::vector<std::size_t> coverage;
	coverage.resize(points.size<1>());

	// using chrono to measure execution time
	auto start = std::chrono::high_resolution_clock::now();
	alg(points, radius, coverage);
	auto end = std::chrono::high_resolution_clock::now();
	auto us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
	std::cout << us << std::endl;

	saveCoverage(outputFile, coverage);
}


int main(int argc, char* argv[]) {
	bpp::ProgramArguments args(2, 2);
	try {
		args.setNamelessCaption(0, "Input 2D points file.");
		args.setNamelessCaption(1, "Output coverage file.");

		args.registerArg<bpp::ProgramArguments::ArgString>("algorithm", "Which algorithm should be executed (serial, sycl).", true);
		args.registerArg<bpp::ProgramArguments::ArgFloat>("r", "Coverage radius.", true, 1.0, 0.001);

		args.process(argc, argv);
	}
	catch (bpp::ArgumentException &e) {
		std::cerr << "Argument error: " << e.what() << std::endl;
		args.printUsage(std::cerr);
		return 1;
	}

	// load data
	Grid<real_t, 2> points;
	try {
		points.load(args[0]);
	}
	catch (std::exception& e) {
		std::cerr << "Error loading points: " << e.what() << std::endl;
		return 1;
	}
	if (points.size<0>() != 2 || points.size<1>() < 5) {
		std::cerr << "Error, input file must be an array of 2D points of minimal size 5." << std::endl;
		return 1;
	}

	real_t radius = (real_t)args.getArgFloat("r").getValue();
	try {
		// run the algorithm
		std::string algorithm = args.getArgString("algorithm").getValue();
		if (algorithm == "serial") {
			run<SerialImplementation<real_t>>(points, radius, args[1]);
		} else if (algorithm == "sycl") {
			run<SyclImplementation<real_t>>(points, radius, args[1]);
		} else {
			std::cerr << "Unknown algorithm '" << args.getArgString("algorithm").getValue() << "'." << std::endl;
			std::cerr << "Supported algorithms: serial, sycl" << std::endl;
			return 1;
		}
	} catch (const std::exception &e) {
		std::cerr << "Error: " << e.what() << std::endl;
		return 2;
	}

	return 0;
}
