#include "converter.hpp"

Converter::Converter() {
	const auto &filePath = config.getString("file");
	if (!boost::filesystem::exists(filePath)) {
		log.error.format("File does not exist \"{:s}\"", filePath);
		throw std::runtime_error("File does not exist");
	}

	fileSize = boost::filesystem::file_size(filePath);
	config.setLong("fileSize", fileSize);
	inputFile = boost::nowide::ifstream(filePath, std::ios::binary);

	if (!inputFile.good()) {
		log.error.format("Could not read file \"{:s}\"", filePath);
		throw std::runtime_error("Could not read file.");
	}

	aedatVersion = readAedatVersion();

	switch (aedatVersion) {
		case 1:
		case 2:
			aedat2Init(filePath);
			break;
		case 3:
			aedat3Init(filePath);
			break;
		case 31:
			aedat31Init(filePath);
			break;
		default:
			throw std::runtime_error("No aedat version set");
	}
}

void Converter::aedat2Run() {
	auto eventOut   = outputs.getEventOutput("events").events();
	auto frameOut   = outputs.getFrameOutput("frames").frame();
	auto imuOut     = outputs.getIMUOutput("imu").data();
	auto triggerOut = outputs.getTriggerOutput("triggers").data();
	long bytesRead  = aedat2Parser->pulse(eventOut, frameOut, imuOut, triggerOut);
	if (bytesRead <= 0) {
		config.setBool("running", false);
	}
	processedSize = std::min(processedSize + bytesRead, fileSize);
	config.setLong("processedSize", processedSize);
}

int Converter::readAedatVersion() {
	// it only makes sense to read as much data as the header line would be
	std::array<char, VERSION_LINE_MAX_LENGTH> lineBuffer;
	inputFile.getline(lineBuffer.data(), VERSION_LINE_MAX_LENGTH);
	std::string line(lineBuffer.data());

	std::regex versionRegex("^#!AER-DAT([0-9]+)\\.([0-9]+)");
	std::smatch matches;

	if (std::regex_search(line, matches, versionRegex)) {
		int32_t majorVersion = std::atoi(matches[1].str().c_str());
		int32_t minorVersion = std::atoi(matches[2].str().c_str());
		switch (majorVersion) {
			case 2:
				return 2;
			case 3:
				if (minorVersion != 1) {
					return 3;
				}
				else {
					return 31;
				}
			default:
				// Versions other than 2.0 and 3.X are not supported.
				return -1;
				break;
		}
	}
	else {
		// in case there is no header, it can still be aedat 1
		return 1;
	}
}

void Converter::run() {
	switch (aedatVersion) {
		case 1:
		case 2:
			aedat2Run();
			break;
		case 3:
			aedat3Run();
			break;
		case 31:
			aedat31Run();
			break;
		default:
			throw std::runtime_error("No aedat version set");
	}
}

void Converter::aedat3Run() {
	auto eventOut   = outputs.getEventOutput("events").events();
	auto frameOut   = outputs.getFrameOutput("frames").frame();
	auto imuOut     = outputs.getIMUOutput("imu").data();
	auto triggerOut = outputs.getTriggerOutput("triggers").data();
	if (!aedat3Parser->get_data(eventOut, frameOut, imuOut, triggerOut)) {
		config.setBool("running", false);
	}
}

void Converter::aedat31Run() {
	auto eventOut   = outputs.getEventOutput("events").events();
	auto frameOut   = outputs.getFrameOutput("frames").frame();
	auto imuOut     = outputs.getIMUOutput("imu").data();
	auto triggerOut = outputs.getTriggerOutput("triggers").data();
	if (!aedat31Parser->get_data(eventOut, frameOut, imuOut, triggerOut)) {
		config.setBool("running", false);
	}
}

void Converter::aedat2Init(const std::string &filePath) {
	aedat2Parser    = std::make_unique<Aedat2Parser>(filePath, &log);
	auto parsePrefs = aedat2Parser->getPrefs();

	std::string sourceInfo = parsePrefs.sourceInfo.empty() ? "UNKNOWN SOURCE" : parsePrefs.sourceInfo;
	outputs.getEventOutput("events").setup(parsePrefs.dataWidth, parsePrefs.dataHeight, parsePrefs.sourceInfo);
	outputs.getFrameOutput("frames").setup(parsePrefs.dataWidth, parsePrefs.dataHeight, parsePrefs.sourceInfo);
	outputs.getIMUOutput("imu").setup(sourceInfo);
	outputs.getTriggerOutput("triggers").setup(sourceInfo);
}

void Converter::aedat31Init(const std::string &filePath) {
	aedat31Parser = std::make_unique<Aedat31Parser>(filePath, &log);
	outputs.getEventOutput("events").setup(
		aedat31Parser->eventSizeX, aedat31Parser->eventSizeY, std::to_string(aedat31Parser->sourceID));
	outputs.getFrameOutput("frames").setup(
		aedat31Parser->apsSizeX, aedat31Parser->apsSizeY, std::to_string(aedat31Parser->sourceID));
	outputs.getIMUOutput("imu").setup(std::to_string(aedat31Parser->sourceID));
	outputs.getTriggerOutput("triggers").setup(std::to_string(aedat31Parser->sourceID));
}

void Converter::aedat3Init(const std::string &filePath) {
	aedat3Parser = std::make_unique<Aedat3Parser>(filePath, &log);
	outputs.getEventOutput("events").setup(
		aedat3Parser->eventSizeX, aedat3Parser->eventSizeY, std::to_string(aedat3Parser->sourceID));
	outputs.getFrameOutput("frames").setup(
		aedat3Parser->apsSizeX, aedat3Parser->apsSizeY, std::to_string(aedat3Parser->sourceID));
	outputs.getIMUOutput("imu").setup(std::to_string(aedat3Parser->sourceID));
	outputs.getTriggerOutput("triggers").setup(std::to_string(aedat3Parser->sourceID));
}
