#pragma once

#include "../../../include/dv-processing/io/camera/dvxplorer.hpp"

#include <pybind11/pybind11.h>

namespace py = pybind11;

/**
 * @brief Bind discoverDevices class to python
 * @param m pybind The module to bind to
 */
void bind_dvxplorer(py::module &m) {
	static dv::io::camera::USBDevice::loggerCallbackType DEFAULT_CLI_LOGGER
		= [](const dv::io::camera::USBDevice::LogLevel level, const std::string_view deviceName,
			  const std::string_view message) {
			  std::cerr << fmt::format("[{}] {}: {}", level, deviceName, message) << std::endl;
		  };

	py::class_<dv::io::camera::DVXplorer, dv::io::camera::USBDevice, dv::io::camera::SyncCameraInputBase> dvxplorer(
		m, "DVXplorer", "DVXplorer camera.");

	py::enum_<dv::io::camera::DVXplorer::SubSample>(dvxplorer, "SubSample")
		.value("EVERY_PIXEL", dv::io::camera::DVXplorer::SubSample::EVERY_PIXEL)
		.value("EVERY_SECOND", dv::io::camera::DVXplorer::SubSample::EVERY_SECOND)
		.value("BAND_OF_TWO", dv::io::camera::DVXplorer::SubSample::BAND_OF_TWO)
		.value("EVERY_FOURTH", dv::io::camera::DVXplorer::SubSample::EVERY_FOURTH)
		.value("BAND_OF_FOUR", dv::io::camera::DVXplorer::SubSample::BAND_OF_FOUR)
		.value("BIN_1010_0000", dv::io::camera::DVXplorer::SubSample::BIN_1010_0000)
		.value("BIN_1100_0000", dv::io::camera::DVXplorer::SubSample::BIN_1100_0000)
		.value("EVERY_EIGHTH", dv::io::camera::DVXplorer::SubSample::EVERY_EIGHTH);

	py::enum_<dv::io::camera::DVXplorer::ReadoutFPS>(dvxplorer, "ReadoutFPS")
		.value("CONSTANT_100", dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_100)
		.value("CONSTANT_200", dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_200)
		.value("CONSTANT_500", dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_500)
		.value("CONSTANT_1000", dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_1000)
		.value("CONSTANT_LOSSY_2000", dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_LOSSY_2000)
		.value("CONSTANT_LOSSY_5000", dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_LOSSY_5000)
		.value("CONSTANT_LOSSY_10000", dv::io::camera::DVXplorer::ReadoutFPS::CONSTANT_LOSSY_10000)
		.value("VARIABLE_2000", dv::io::camera::DVXplorer::ReadoutFPS::VARIABLE_2000)
		.value("VARIABLE_5000", dv::io::camera::DVXplorer::ReadoutFPS::VARIABLE_5000)
		.value("VARIABLE_10000", dv::io::camera::DVXplorer::ReadoutFPS::VARIABLE_10000)
		.value("VARIABLE_15000", dv::io::camera::DVXplorer::ReadoutFPS::VARIABLE_15000);

	dvxplorer.def_static(
		"findDevices",
		[](const std::string_view filterBySerialNumber) {
			return dv::io::camera::DVXplorer::findDevices(filterBySerialNumber, DEFAULT_CLI_LOGGER);
		},
		py::arg("filterBySerialNumber") = "", "Find connected DVXplorer cameras.");

	// Events.
	dvxplorer
		.def(py::init([](const dv::io::camera::USBDevice::LogLevel deviceLogLevel) {
			return std::make_unique<dv::io::camera::DVXplorer>(DEFAULT_CLI_LOGGER, deviceLogLevel);
		}),
			py::arg("deviceLogLevel") = dv::io::camera::USBDevice::LogLevel::LVL_WARNING,
			"Open the first DVXplorer camera that can be found.")
		.def(py::init([](const std::string_view filterBySerialNumber,
						  const dv::io::camera::USBDevice::LogLevel deviceLogLevel) {
			return std::make_unique<dv::io::camera::DVXplorer>(
				filterBySerialNumber, DEFAULT_CLI_LOGGER, deviceLogLevel);
		}),
			py::arg("filterBySerialNumber"),
			py::arg("deviceLogLevel") = dv::io::camera::USBDevice::LogLevel::LVL_WARNING,
			"Open the DVXplorer camera with the specified serial number.")
		.def(py::init([](const dv::io::camera::USBDevice::DeviceDescriptor &deviceToOpen,
						  const dv::io::camera::USBDevice::LogLevel deviceLogLevel) {
			return std::make_unique<dv::io::camera::DVXplorer>(deviceToOpen, DEFAULT_CLI_LOGGER, deviceLogLevel);
		}),
			py::arg("deviceToOpen"), py::arg("deviceLogLevel") = dv::io::camera::USBDevice::LogLevel::LVL_WARNING,
			"Open the DVXplorer camera corresponding to the specified descriptor.")
		.def("getLogicVersion", &dv::io::camera::DVXplorer::getLogicVersion, "Get camera FPGA logic version.")
		.def("getLogicPatchLevel", &dv::io::camera::DVXplorer::getLogicPatchLevel, "Get camera FPGA logic patch level.")
		.def("isEventsRunning", &dv::io::camera::DVXplorer::isEventsRunning, "Report if the event output is running.")
		.def("setEventsRunning", &dv::io::camera::DVXplorer::setEventsRunning, py::arg("run"),
			"Enable or disable the event output.")
		.def("getSubSampleHorizontal", &dv::io::camera::DVXplorer::getSubSampleHorizontal,
			"Get horizontal subsampling mode.")
		.def("setSubSampleHorizontal", &dv::io::camera::DVXplorer::setSubSampleHorizontal,
			py::arg("subSampleHorizontal"), "Set horizontal subsampling mode.")
		.def("getSubSampleVertical", &dv::io::camera::DVXplorer::getSubSampleVertical, "Get vertical subsampling mode.")
		.def("setSubSampleVertical", &dv::io::camera::DVXplorer::setSubSampleVertical, py::arg("subSampleVertical"),
			"Set vertical subsampling mode.")
		.def("getDualBinning", &dv::io::camera::DVXplorer::getDualBinning, "Report status of dual-binning feature.")
		.def("setDualBinning", &dv::io::camera::DVXplorer::setDualBinning, py::arg("dualBinning"),
			"Enable dual-binning, maps 2x2 pixel blocks to one pixel address.")
		.def("getGlobalHold", &dv::io::camera::DVXplorer::getGlobalHold, "Report status of global hold feature.")
		.def("setGlobalHold", &dv::io::camera::DVXplorer::setGlobalHold, py::arg("globalHold"),
			"Enable or disable global hold feature. Default is enabled.")
		.def("getGlobalReset", &dv::io::camera::DVXplorer::getGlobalReset, "Report status of global reset feature.")
		.def("setGlobalReset", &dv::io::camera::DVXplorer::setGlobalReset, py::arg("globalReset"),
			"Enable or disable global reset feature.")
		.def("getReadoutFPS", &dv::io::camera::DVXplorer::getReadoutFPS,
			"Get currently set event-frame readout frequency.")
		.def("setReadoutFPS", &dv::io::camera::DVXplorer::setReadoutFPS, py::arg("readoutFps"),
			"Set frequency of event-frame readouts on sensor.")
		.def("getContrastThresholdOn", &dv::io::camera::DVXplorer::getContrastThresholdOn,
			"Get the contrast threshold for ON polarity event generation.")
		.def("setContrastThresholdOn", &dv::io::camera::DVXplorer::setContrastThresholdOn,
			py::arg("contrastThresholdOn"), "Set the contrast threshold for ON polarity event generation.")
		.def("getContrastThresholdOff", &dv::io::camera::DVXplorer::getContrastThresholdOff,
			"Get the contrast threshold for OFF polarity event generation.")
		.def("setContrastThresholdOff", &dv::io::camera::DVXplorer::setContrastThresholdOff,
			py::arg("contrastThresholdOff"), "Set the contrast threshold for OFF polarity event generation.");

	// IMU.
	dvxplorer
		.def("isImuRunningAccelerometer", &dv::io::camera::DVXplorer::isImuRunningAccelerometer,
			"Status of IMU accelerometer.")
		.def("setImuRunningAccelerometer", &dv::io::camera::DVXplorer::setImuRunningAccelerometer, py::arg("run"),
			"Enable or disable IMU accelerometer.")
		.def("isImuRunningGyroscope", &dv::io::camera::DVXplorer::isImuRunningGyroscope, "Status of IMU gyroscope.")
		.def("setImuRunningGyroscope", &dv::io::camera::DVXplorer::setImuRunningGyroscope, py::arg("run"),
			"Enable or disable IMU gyroscope.")
		.def("isImuRunningTemperature", &dv::io::camera::DVXplorer::isImuRunningTemperature,
			"Status of IMU temperature measurement.")
		.def("setImuRunningTemperature", &dv::io::camera::DVXplorer::setImuRunningTemperature, py::arg("run"),
			"Enable or disable IMU temperature measurement.")
		.def("getIMUFlipX", &dv::io::camera::DVXplorer::getIMUFlipX, "Status of IMU X axis flipping.")
		.def("setIMUFlipX", &dv::io::camera::DVXplorer::setIMUFlipX, py::arg("flipX"),
			"Enable or disable IMU X axis flipping.")
		.def("getIMUFlipY", &dv::io::camera::DVXplorer::getIMUFlipY, "Status of IMU Y axis flipping.")
		.def("setIMUFlipY", &dv::io::camera::DVXplorer::setIMUFlipY, py::arg("flipY"),
			"Enable or disable IMU Y axis flipping.")
		.def("getIMUFlipZ", &dv::io::camera::DVXplorer::getIMUFlipZ, "Status of IMU Z axis flipping.")
		.def("setIMUFlipZ", &dv::io::camera::DVXplorer::setIMUFlipZ, py::arg("flipZ"),
			"Enable or disable IMU Z axis flipping.")
		.def("getImuAccelDataRate", &dv::io::camera::DVXplorer::getImuAccelDataRate,
			"Get current IMU accelerometer data rate.")
		.def("setImuAccelDataRate", &dv::io::camera::DVXplorer::setImuAccelDataRate, py::arg("dataRate"),
			"Set IMU accelerometer data rate.")
		.def("getImuAccelFilter", &dv::io::camera::DVXplorer::getImuAccelFilter,
			"Get current IMU accelerometer filter setting.")
		.def("setImuAccelFilter", &dv::io::camera::DVXplorer::setImuAccelFilter, py::arg("filter"),
			"Set IMU accelerometer filter setting.")
		.def("getImuAccelRange", &dv::io::camera::DVXplorer::getImuAccelRange, "Get current IMU accelerometer range.")
		.def("setImuAccelRange", &dv::io::camera::DVXplorer::setImuAccelRange, py::arg("range"),
			"Set IMU accelerometer range.")
		.def("getImuGyroDataRate", &dv::io::camera::DVXplorer::getImuGyroDataRate,
			"Get current IMU gyroscope data rate.")
		.def("setImuGyroDataRate", &dv::io::camera::DVXplorer::setImuGyroDataRate, py::arg("dataRate"),
			"Set IMU gyroscope data rate.")
		.def("getImuGyroFilter", &dv::io::camera::DVXplorer::getImuGyroFilter,
			"Get current IMU gyroscope filter setting.")
		.def("setImuGyroFilter", &dv::io::camera::DVXplorer::setImuGyroFilter, py::arg("filter"),
			"Set IMU gyroscope filter setting.")
		.def("getImuGyroRange", &dv::io::camera::DVXplorer::getImuGyroRange, "Get current IMU gyroscope range.")
		.def("setImuGyroRange", &dv::io::camera::DVXplorer::setImuGyroRange, py::arg("range"),
			"Set IMU gyroscope range.");

	// External SYNC connectors.
	dvxplorer
		.def("isDetectorRunning", &dv::io::camera::DVXplorer::isDetectorRunning,
			"Report status of external signal detector.")
		.def("setDetectorRunning", &dv::io::camera::DVXplorer::setDetectorRunning, py::arg("run"),
			"Enable or disable external signal detector.")
		.def("getDetectorRisingEdges", &dv::io::camera::DVXplorer::getDetectorRisingEdges,
			"Report status of rising edge detection on the SIGNAL_IN line.")
		.def("setDetectorRisingEdges", &dv::io::camera::DVXplorer::setDetectorRisingEdges, py::arg("detectRising"),
			"Detect rising edges (low to high transitions) on the SIGNAL_IN line.")
		.def("getDetectorFallingEdges", &dv::io::camera::DVXplorer::getDetectorFallingEdges,
			"Report status of falling edge detection on the SIGNAL_IN line.")
		.def("setDetectorFallingEdges", &dv::io::camera::DVXplorer::setDetectorFallingEdges, py::arg("detectFalling"),
			"Detect falling edges (high to low transitions) on the SIGNAL_IN line.")
		.def("getGeneratorInjectTriggerOnRisingEdge", &dv::io::camera::DVXplorer::getGeneratorInjectTriggerOnRisingEdge,
			"Report status of trigger event injection feature for external signal generator rising edges.")
		.def("setGeneratorInjectTriggerOnRisingEdge", &dv::io::camera::DVXplorer::setGeneratorInjectTriggerOnRisingEdge,
			py::arg("injectRising"),
			"Inject a trigger event of type EXTERNAL_GENERATOR_RISING_EDGE into the event stream from the device.")
		.def("getGeneratorInjectTriggerOnFallingEdge",
			&dv::io::camera::DVXplorer::getGeneratorInjectTriggerOnFallingEdge,
			"Report status of trigger event injection feature for external signal generator falling edges.")
		.def("setGeneratorInjectTriggerOnFallingEdge",
			&dv::io::camera::DVXplorer::setGeneratorInjectTriggerOnFallingEdge, py::arg("injectFalling"),
			"Inject a trigger event of type EXTERNAL_GENERATOR_FALLING_EDGE into the event stream from the device.")
		.def("isGeneratorRunning", &dv::io::camera::DVXplorer::isGeneratorRunning,
			"Report status of external signal generator.")
		.def("setGeneratorRunning", &dv::io::camera::DVXplorer::setGeneratorRunning, py::arg("run"),
			"Enable or disable external signal generator (PWM-like output).")
		.def("getGeneratorHighTime", &dv::io::camera::DVXplorer::getGeneratorHighTime, "Get current PWM high time.")
		.def("setGeneratorHighTime", &dv::io::camera::DVXplorer::setGeneratorHighTime, py::arg("highTimeUs"),
			"Set PWM high time for external signal generator.")
		.def("getGeneratorLowTime", &dv::io::camera::DVXplorer::getGeneratorLowTime, "Get current PWM low time.")
		.def("setGeneratorLowTime", &dv::io::camera::DVXplorer::setGeneratorLowTime, py::arg("lowTimeUs"),
			"Set PWM low time for external signal generator.");

	// USB.
	dvxplorer
		.def("getUSBEarlyPacketDelay", &dv::io::camera::DVXplorer::getUSBEarlyPacketDelay,
			"Get value of USB early packet timeout.")
		.def("setUSBEarlyPacketDelay", &dv::io::camera::DVXplorer::setUSBEarlyPacketDelay,
			py::arg("earlyPacketDelayUs"),
			"Send data over USB early if this timeout is reached, instead of waiting on buffers being full.");
}
