#include "serial.hpp"
#include "parallel.hpp"
#include "data.hpp"
#include "args.hpp"

#include <iostream>
#include <string>
#include <chrono>

/**
 * Run selected algorithm and save its results to a file.
 * Measured time in us is printed to the standard output.
 */
template<class ALG>
void run(const items_t &items, std::uint64_t minSize, std::uint64_t maxSize, std::string resultFile)
{
	ALG alg(items.size(), minSize, maxSize);
	result_t result;

	// run given functor and measure its time
	const auto start = std::chrono::high_resolution_clock::now();
	std::uint64_t priority = alg(items, result);
	const 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;

	std::sort(result.begin(), result.end());
	saveResults(result, priority, items.size(), resultFile);
}


int main(int argc, char *argv[])
{
	bpp::ProgramArguments args(2, 2);
	args.setNamelessCaption(0, "input items file");
	args.setNamelessCaption(1, "output selection file");
	try {
		args.registerArg<bpp::ProgramArguments::ArgString>("algorithm", "Which algorithm should be executed (serial, parallel).", true);
		args.registerArg<bpp::ProgramArguments::ArgInt>("min", "Minimal required size of the selection.", false, 9900000, 1);
		args.registerArg<bpp::ProgramArguments::ArgInt>("max", "Maximal required size of the selection.", false, 10000000, 1);

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

	items_t items;
	loadItems(args[0], items);
	std::uint64_t minSize = (std::uint64_t)args.getArgInt("min").getValue();
	std::uint64_t maxSize = (std::uint64_t)args.getArgInt("max").getValue();
	if (maxSize < minSize) {
		std::cerr << "Argument error: max size cannot be smaller than min size." << std::endl;
		return 1;
	}

	std::string algorithm = args.getArgString("algorithm").getValue();

	try {
		if (algorithm == "serial") {
			run<SerialImplementation>(items, minSize, maxSize, args[1]);
		} else if (algorithm == "parallel") {
			run<ParallelImplementation>(items, minSize, maxSize, args[1]);
		} else {
			std::cerr << "Unknown algorithm: " << algorithm << std::endl;
			std::cerr << "The algorithm value should be either serial, parallel, or verify." << std::endl;
			return 1;
		}

	} catch (const std::exception &e) {
		std::cerr << "Error: " << e.what() << std::endl;
		return 2;
	}
}
