#include "../../include/dv-sdk/data/event.hpp"
#include "../../include/dv-sdk/data/imu.hpp"

#include "../../src/log.hpp"
#include "common.hpp"

#include <dv-processing/io/camera/dvxplorer_m.hpp>

void *dvModuleGetHooks(enum dvModuleHooks hook) {
	// Support only device discovery.
	if (hook != DV_HOOK_DEVICE_DISCOVERY) {
		return nullptr;
	}

	// Get current device status.
	const auto devices = dv::io::camera::DVXplorerM::findDevices();

	// Add devices to config tree.
	auto devicesNode = dv::Cfg::GLOBAL.getNode("/system/_devices/");

	for (const auto &dev : devices) {
		const auto nodeName
			= fmt::format("dvxplorer_m_{:d}-{:d}/", static_cast<int>(dev.busNumber), static_cast<int>(dev.devAddress));

		auto devNode = devicesNode.getRelativeNode(nodeName);

		devNode.create<dv::CfgType::STRING>("OpenWithModule", "dv_dvxplorer_m", {1, 32},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Open device with specified module.");

		devNode.create<dv::CfgType::INT>("USBBusNumber", dev.busNumber, {0, 255},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "USB bus number.");
		devNode.create<dv::CfgType::INT>("USBDeviceAddress", dev.devAddress, {0, 255},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "USB device address.");

		devNode.create<dv::CfgType::STRING>("SerialNumber", dev.serialNumber, {0, 8},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "USB device serial number.");
		devNode.create<dv::CfgType::INT>("FirmwareVersion", dev.firmwareVersion, {0, INT16_MAX},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Version of device firmware.");
	}

	// Nothing to return, no further steps needed.
	return nullptr;
}

namespace dv_runtime {

class DVXplorerM final : public dv::ModuleBase {
private:
	dv::io::camera::DVXplorerM mDevice;
	bool mIsHDSensor{false};
	std::mutex mConfigLock{};

public:
	static void initOutputs(dv::OutputDefinitionList &out) {
		out.addEventOutput("events");
		out.addIMUOutput("imu");
	}

	static const char *initDescription() {
		return "iniVation DVXplorer Mini/Micro camera support.";
	}

	static void initConfigOptions(dv::RuntimeConfig &config) {
		config.add("serialNumber", dv::ConfigOption::stringOption("USB serial number restriction.", ""));

		// TODO: cannot reload updated values easily into DV.
		// config.add("loadFromFlash", dv::ConfigOption::buttonOption("Reload configuration from device flash memory.",
		// "Reload config from flash"));

		config.add("saveToFlash",
			dv::ConfigOption::buttonOption("Save configuration to device flash memory.", "Save config to flash"));

		configEventsCreate(config);
		configIMUCreate(config);
		configSystemCreate(config);
	}

	DVXplorerM() :
		mDevice(
			config.getString("serialNumber"),
			[this](const dv::io::camera::USBDevice::LogLevel level, const std::string_view deviceName,
				const std::string_view message) {
				switch (level) {
					case dv::io::camera::USBDevice::LogLevel::LVL_ERROR:
						log.error(fmt::format("{}: {}", deviceName, message));
						break;

					case dv::io::camera::USBDevice::LogLevel::LVL_WARNING:
						log.warning(fmt::format("{}: {}", deviceName, message));
						break;

					case dv::io::camera::USBDevice::LogLevel::LVL_INFO:
						log.info(fmt::format("{}: {}", deviceName, message));
						break;

					default:
					case dv::io::camera::USBDevice::LogLevel::LVL_DEBUG:
						log.debug(fmt::format("{}: {}", deviceName, message));
						break;
				}
			},
			deviceLogLevelConvert(dv::LoggerInternal::logLevelNameToEnum(config.getString("logLevel")))) {
		// Setup outputs.
		outputs.getEventOutput("events").setup(
			mDevice.getEventResolution()->width, mDevice.getEventResolution()->height, mDevice.getCameraName());
		outputs.getIMUOutput("imu").setup(mDevice.getCameraName());

		mIsHDSensor = (mDevice.getEventResolution()->width == dv::io::camera::parser::S5KRC1S::WIDTH);

		auto sourceInfoNode = moduleNode.getRelativeNode("sourceInfo/");

		sourceInfoNode.create<dv::CfgType::STRING>("serialNumber", mDevice.getSerialNumber(), {0, 8},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Device serial number.");
		sourceInfoNode.create<dv::CfgType::INT>("usbBusNumber", mDevice.getDeviceDescriptor().busNumber, {0, 255},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Device USB bus number.");
		sourceInfoNode.create<dv::CfgType::INT>("usbDeviceAddress", mDevice.getDeviceDescriptor().devAddress, {0, 255},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Device USB device address.");

		sourceInfoNode.create<dv::CfgType::INT>("firmwareVersion", mDevice.getFirmwareVersion(),
			{mDevice.getFirmwareVersion(), mDevice.getFirmwareVersion()},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Device USB firmware version.");

		sourceInfoNode.create<dv::CfgType::STRING>("source", mDevice.getCameraName(),
			{static_cast<int32_t>(mDevice.getCameraName().length()),
				static_cast<int32_t>(mDevice.getCameraName().length())},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Device source information.");

		sourceInfoNode.create<dv::CfgType::LONG>("tsOffset", mDevice.getTimestampOffset().count(), {0, INT64_MAX},
			dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT,
			"Time offset of data stream starting point to Unix time in µs.");

		// Create default device-dependant settings.
		configEventsCreateDynamic();

		// Initial configuration, since we don't use configUpdate() here.
		configSystem();
		moduleNode.getRelativeNode("system/").addAttributeListener(this, &configSystemTrampoline);

		configEvents();
		moduleNode.getRelativeNode("events/").addAttributeListener(this, &configEventsTrampoline);

		configIMU();
		moduleNode.getRelativeNode("imu/").addAttributeListener(this, &configIMUTrampoline);
	}

	~DVXplorerM() override {
		moduleNode.getRelativeNode("system/").removeAttributeListener(this, &configSystemTrampoline);

		moduleNode.getRelativeNode("events/").removeAttributeListener(this, &configEventsTrampoline);

		moduleNode.getRelativeNode("imu/").removeAttributeListener(this, &configIMUTrampoline);

		// Data capture stopped already in respective destructors.
		// Clear sourceInfo node.
		auto sourceInfoNode = moduleNode.getRelativeNode("sourceInfo/");
		sourceInfoNode.removeAllAttributes();
	}

	void run() override {
		if (!mDevice.isRunning()) {
			// Device gone, stop module.
			config.setBool("running", false);
			return;
		}

		bool gotData{false};

		auto events = mDevice.getNextEventPacket();

		if (events) {
			gotData = true;

			// Send data out.
			auto eventsOutput              = outputs.getEventOutput("events").events();
			*eventsOutput.getBasePointer() = std::move(*events);
			eventsOutput.commit();
		}

		auto imu = mDevice.getNextImuBatch();

		if (imu) {
			gotData = true;

			// Send data out.
			auto imuOutput                       = outputs.getIMUOutput("imu").data();
			imuOutput.getBasePointer()->elements = std::move(*imu);
			imuOutput.commit();
		}

		// No data at all ready during this pass, sleep a bit to reduce load.
		if (!gotData) {
			std::this_thread::sleep_for(std::chrono::microseconds{500});
		}
	}

	void configUpdate() override {
		std::scoped_lock lock(mConfigLock);

		mDevice.setLogLevel(
			deviceLogLevelConvert(dv::LoggerInternal::logLevelNameToEnum(config.getString("logLevel"))));

		if (config.getBool("saveToFlash")) {
			config.setBool("saveToFlash", false); // Reset button.

			mDevice.configStoreToFlash();
		}
	}

private:
	static void configEventsCreate(dv::RuntimeConfig &config) {
		config.add("events/flatten",
			dv::ConfigOption::listOption("Flatten events control.", 0, {"none", "all ON", "ON only", "OFF only"}));

		config.add("events/subsampleHorizontal",
			dv::ConfigOption::listOption("Horizontal sub-sampling factor.", 0, {"none", "1/2", "1/4", "1/8"}));
		config.add("events/subsampleVertical",
			dv::ConfigOption::listOption("Vertical sub-sampling factor.", 0, {"none", "1/2", "1/4", "1/8"}));

		config.add("events/globalReset", dv::ConfigOption::boolOption("Enable global reset.", false));
		config.add("events/globalHold", dv::ConfigOption::boolOption("Enable global hold.", true));

		config.setPriorityOptions({"events/"});
	}

	void configEventsCreateDynamic() {
		// Get default flip orientation from initial configuration of device.
		config.add(
			"events/flipHorizontal", dv::ConfigOption::boolOption("Flip event coordinates along horizontal X axis.",
										 mDevice.getFlipHorizontal()));
		config.add("events/flipVertical",
			dv::ConfigOption::boolOption("Flip event coordinates along vertical Y axis.", mDevice.getFlipVertical()));

		if (!mIsHDSensor) {
			config.add("events/readoutFPS",
				dv::ConfigOption::listOption("Maximum number of event-frames per second (readout speed).",
					"5000 (variable)",
					{"100 (constant)", "200 (constant)", "500 (constant)", "1000 (constant)", "2000 (constant lossy)",
						"5000 (constant lossy)", "10000 (constant lossy)", "2000 (variable)", "5000 (variable)",
						"10000 (variable)", "15000 (variable)"}));
		}

		if (mIsHDSensor) {
			config.add(
				"events/contrastThresholdOn", dv::ConfigOption::intOption("Positive event threshold.", 70, 0, 127));
			config.add(
				"events/contrastThresholdOff", dv::ConfigOption::intOption("Negative event threshold.", 50, 0, 127));
		}
		else {
			config.add(
				"events/contrastThresholdOn", dv::ConfigOption::intOption("Positive event threshold.", 9, 0, 17));
			config.add(
				"events/contrastThresholdOff", dv::ConfigOption::intOption("Negative event threshold.", 9, 0, 17));
		}

		config.add("events/cropStartX", dv::ConfigOption::intOption("Horizontal (X axis) start of ROI.", 0, 0,
											mDevice.getEventResolution()->width - 1));
		config.add("events/cropStartY", dv::ConfigOption::intOption("Vertical (Y axis) start of ROI.", 0, 0,
											mDevice.getEventResolution()->height - 1));
		config.add("events/cropWidth", dv::ConfigOption::intOption("ROI width.", mDevice.getEventResolution()->width, 1,
										   mDevice.getEventResolution()->width));
		config.add("events/cropHeight", dv::ConfigOption::intOption("ROI height.", mDevice.getEventResolution()->height,
											1, mDevice.getEventResolution()->height));

		config.setPriorityOptions({"events/readoutFPS", "events/contrastThresholdOn", "events/contrastThresholdOff"});
	}

	static dv::io::camera::CameraInputBase::Flatten mapFlatten(const std::string &strVal) {
		if (strVal == "all ON") {
			return dv::io::camera::CameraInputBase::Flatten::FLATTEN_ON;
		}
		else if (strVal == "ON only") {
			return dv::io::camera::CameraInputBase::Flatten::ON_ONLY;
		}
		else if (strVal == "OFF only") {
			return dv::io::camera::CameraInputBase::Flatten::OFF_ONLY;
		}
		else {
			// Default: no flattening.
			return dv::io::camera::CameraInputBase::Flatten::NONE;
		}
	}

	static dv::io::camera::DVXplorer::SubSample mapSubsampleFactor(const std::string &strVal) {
		if (strVal == "1/2") {
			return dv::io::camera::DVXplorer::SubSample::EVERY_SECOND;
		}
		else if (strVal == "1/4") {
			return dv::io::camera::DVXplorer::SubSample::EVERY_FOURTH;
		}
		else if (strVal == "1/8") {
			return dv::io::camera::DVXplorer::SubSample::EVERY_EIGHTH;
		}
		else {
			// Default: no sub-sampling.
			return dv::io::camera::DVXplorer::SubSample::EVERY_PIXEL;
		}
	}

	static dv::io::camera::DVXplorer::ReadoutFPS mapReadoutFPS(const std::string &strVal) {
		if (strVal == "100 (constant)") {
			return dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_100;
		}
		else if (strVal == "200 (constant)") {
			return dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_200;
		}
		else if (strVal == "500 (constant)") {
			return dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_500;
		}
		else if (strVal == "1000 (constant)") {
			return dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_1000;
		}
		else if (strVal == "2000 (constant lossy)") {
			return dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_LOSSY_2000;
		}
		else if (strVal == "5000 (constant lossy)") {
			return dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_LOSSY_5000;
		}
		else if (strVal == "10000 (constant lossy)") {
			return dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_LOSSY_10000;
		}
		else if (strVal == "2000 (variable)") {
			return dv::io::camera::DVXplorer::ReadoutFPS::VARIABLE_2000;
		}
		else if (strVal == "10000 (variable)") {
			return dv::io::camera::DVXplorer::ReadoutFPS::VARIABLE_10000;
		}
		else if (strVal == "15000 (variable)") {
			return dv::io::camera::DVXplorer::ReadoutFPS::VARIABLE_15000;
		}

		// Default: 5000 FPS.
		return dv::io::camera::DVXplorer::ReadoutFPS::VARIABLE_5000;
	}

	static void configEventsTrampoline(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
		const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue) {
		auto *obj = static_cast<DVXplorerM *>(userData);

		obj->configEvents();
	}

	void configEvents() {
		std::scoped_lock lock(mConfigLock);
		config.update();

		const auto flatten = config.getString("events/flatten");
		mDevice.setFlatten(mapFlatten(flatten));

		const auto subsampleHorizontal = config.getString("events/subsampleHorizontal");
		mDevice.setSubSampleHorizontal(mapSubsampleFactor(subsampleHorizontal));

		const auto subsampleVertical = config.getString("events/subsampleVertical");
		mDevice.setSubSampleVertical(mapSubsampleFactor(subsampleVertical));

		mDevice.setGlobalReset(config.getBool("events/globalReset"));
		mDevice.setGlobalHold(config.getBool("events/globalHold"));

		if (!mIsHDSensor) {
			const auto readoutFPS = config.getString("events/readoutFPS");
			mDevice.setReadoutFPS(mapReadoutFPS(readoutFPS));
		}

		mDevice.setContrastThresholdOn(config.getInt("events/contrastThresholdOn"));
		mDevice.setContrastThresholdOff(config.getInt("events/contrastThresholdOff"));

		mDevice.setFlipHorizontal(config.getBool("events/flipHorizontal"));
		mDevice.setFlipVertical(config.getBool("events/flipVertical"));

		try {
			mDevice.setCropArea(cv::Rect{config.getInt("events/cropStartX"), config.getInt("events/cropStartY"),
				config.getInt("events/cropWidth"), config.getInt("events/cropHeight")});
		}
		catch (const std::exception &) {
			log.warning("Invalid events crop area setting, verify that (cropStartX + cropWidth) is smaller than sensor "
						"width and that (cropStartY + cropHeight) is smaller than sensor height.");
		}
	}

	static void configIMUCreate(dv::RuntimeConfig &config) {
		config.add("imu/runAccelerometer", dv::ConfigOption::boolOption("Enable accelerometer.", true));
		config.add("imu/runGyroscope", dv::ConfigOption::boolOption("Enable gyroscope.", true));
		config.add("imu/runTemperature", dv::ConfigOption::boolOption("Enable temperature sensor.", true));
		config.add(
			"imu/accelDataRate", dv::ConfigOption::listOption("Accelerometer bandwidth configuration.", "800 Hz",
									 {"12.5 Hz", "25 Hz", "50 Hz", "100 Hz", "200 Hz", "400 Hz", "800 Hz", "1600 Hz"}));
		config.add("imu/accelFilter",
			dv::ConfigOption::listOption("Accelerometer filter configuration.", "Normal", {"Normal", "OSR2", "OSR4"}));
		config.add("imu/accelRange",
			dv::ConfigOption::listOption("Accelerometer range configuration.", "±4G", {"±2G", "±4G", "±8G", "±16G"}));
		config.add(
			"imu/gyroDataRate", dv::ConfigOption::listOption("Gyroscope bandwidth configuration.", "800 Hz",
									{"25 Hz", "50 Hz", "100 Hz", "200 Hz", "400 Hz", "800 Hz", "1600 Hz", "3200 Hz"}));
		config.add("imu/gyroFilter",
			dv::ConfigOption::listOption("Gyroscope filter configuration.", "Normal", {"Normal", "OSR2", "OSR4"}));
		config.add("imu/gyroRange", dv::ConfigOption::listOption("Gyroscope range configuration.", "±500°/s",
										{"±125°/s", "±250°/s", "±500°/s", "±1000°/s", "±2000°/s"}));

		config.setPriorityOptions({"imu/runAccelerometer", "imu/runGyroscope"});
	}

	static dv::io::camera::imu::BoschBMI160AccelDataRate mapAccelDataRate(const std::string &strVal) {
		if (strVal == "12.5 Hz") {
			return dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_12_5HZ;
		}
		else if (strVal == "25 Hz") {
			return dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_25HZ;
		}
		else if (strVal == "50 Hz") {
			return dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_50HZ;
		}
		else if (strVal == "100 Hz") {
			return dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_100HZ;
		}
		else if (strVal == "200 Hz") {
			return dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_200HZ;
		}
		else if (strVal == "400 Hz") {
			return dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_400HZ;
		}
		else if (strVal == "800 Hz") {
			return dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_800HZ;
		}
		else {
			return dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_1600HZ;
		}
	}

	static dv::io::camera::imu::BoschBMI160AccelFilter mapAccelFilter(const std::string &strVal) {
		if (strVal == "Normal") {
			return dv::io::camera::imu::BoschBMI160AccelFilter::FILTER_NORMAL;
		}
		else if (strVal == "OSR2") {
			return dv::io::camera::imu::BoschBMI160AccelFilter::FILTER_OSR2;
		}
		else {
			return dv::io::camera::imu::BoschBMI160AccelFilter::FILTER_OSR4;
		}
	}

	static dv::io::camera::imu::BoschBMI160AccelRange mapAccelRange(const std::string &strVal) {
		if (strVal == "±2G") {
			return dv::io::camera::imu::BoschBMI160AccelRange::RANGE_2G;
		}
		else if (strVal == "±4G") {
			return dv::io::camera::imu::BoschBMI160AccelRange::RANGE_4G;
		}
		else if (strVal == "±8G") {
			return dv::io::camera::imu::BoschBMI160AccelRange::RANGE_8G;
		}
		else {
			return dv::io::camera::imu::BoschBMI160AccelRange::RANGE_16G;
		}
	}

	static dv::io::camera::imu::BoschBMI160GyroDataRate mapGyroDataRate(const std::string &strVal) {
		if (strVal == "25 Hz") {
			return dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_25HZ;
		}
		else if (strVal == "50 Hz") {
			return dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_50HZ;
		}
		else if (strVal == "100 Hz") {
			return dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_100HZ;
		}
		else if (strVal == "200 Hz") {
			return dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_200HZ;
		}
		else if (strVal == "400 Hz") {
			return dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_400HZ;
		}
		else if (strVal == "800 Hz") {
			return dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_800HZ;
		}
		else if (strVal == "1600 Hz") {
			return dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_1600HZ;
		}
		else {
			return dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_3200HZ;
		}
	}

	static dv::io::camera::imu::BoschBMI160GyroFilter mapGyroFilter(const std::string &strVal) {
		if (strVal == "Normal") {
			return dv::io::camera::imu::BoschBMI160GyroFilter::FILTER_NORMAL;
		}
		else if (strVal == "OSR2") {
			return dv::io::camera::imu::BoschBMI160GyroFilter::FILTER_OSR2;
		}
		else {
			return dv::io::camera::imu::BoschBMI160GyroFilter::FILTER_OSR4;
		}
	}

	static dv::io::camera::imu::BoschBMI160GyroRange mapGyroRange(const std::string &strVal) {
		if (strVal == "±125°/s") {
			return dv::io::camera::imu::BoschBMI160GyroRange::RANGE_125DPS;
		}
		else if (strVal == "±250°/s") {
			return dv::io::camera::imu::BoschBMI160GyroRange::RANGE_250DPS;
		}
		else if (strVal == "±500°/s") {
			return dv::io::camera::imu::BoschBMI160GyroRange::RANGE_500DPS;
		}
		else if (strVal == "±1000°/s") {
			return dv::io::camera::imu::BoschBMI160GyroRange::RANGE_1000DPS;
		}
		else {
			return dv::io::camera::imu::BoschBMI160GyroRange::RANGE_2000DPS;
		}
	}

	static void configIMUTrampoline(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
		const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue) {
		auto *obj = static_cast<DVXplorerM *>(userData);

		obj->configIMU();
	}

	void configIMU() {
		std::scoped_lock lock(mConfigLock);
		config.update();

		mDevice.setImuAccelDataRate(mapAccelDataRate(config.getString("imu/accelDataRate")));
		mDevice.setImuAccelFilter(mapAccelFilter(config.getString("imu/accelFilter")));
		mDevice.setImuAccelRange(mapAccelRange(config.getString("imu/accelRange")));
		mDevice.setImuGyroDataRate(mapGyroDataRate(config.getString("imu/gyroDataRate")));
		mDevice.setImuGyroFilter(mapGyroFilter(config.getString("imu/gyroFilter")));
		mDevice.setImuGyroRange(mapGyroRange(config.getString("imu/gyroRange")));

		mDevice.setImuRunningAccelerometer(config.getBool("imu/runAccelerometer"));
		mDevice.setImuRunningGyroscope(config.getBool("imu/runGyroscope"));
		mDevice.setImuRunningTemperature(config.getBool("imu/runTemperature"));
	}

	static void configSystemCreate(dv::RuntimeConfig &config) {
		// USB buffer settings.
		config.add("system/usbTransfersNumber", dv::ConfigOption::intOption("Number of USB transfers.", 32, 2, 128));
		config.add("system/usbTransfersSize",
			dv::ConfigOption::intOption("Size in bytes of data buffers for USB transfers.", 8 * 1024, 512, 64 * 1024));

		config.add(
			"system/timeInterval", dv::ConfigOption::intOption("Time interval in µs for data parsing and forwarding.",
									   10'000, 1, 120 * 1000 * 1000));

		config.setPriorityOptions({"system/"});
	}

	static void configSystemTrampoline(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
		const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue) {
		auto *obj = static_cast<DVXplorerM *>(userData);

		obj->configSystem();
	}

	void configSystem() {
		std::scoped_lock lock(mConfigLock);
		config.update();

		mDevice.setTransfersNumber(config.getInt("system/usbTransfersNumber"));
		mDevice.setTransfersSize(config.getInt("system/usbTransfersSize"));

		mDevice.setTimeInterval(std::chrono::microseconds{config.getInt("system/timeInterval")});
	}
};

} // namespace dv_runtime

registerModuleClass(dv_runtime::DVXplorerM)
