#pragma once

#include "../../../include/dv-processing/io/camera/camera_input_base.hpp"
#include "../../../include/dv-processing/io/camera/camera_model.hpp"
#include "../../../include/dv-processing/io/camera/imu_support.hpp"
#include "../../../include/dv-processing/io/camera/sync_camera_input_base.hpp"
#include "../../../include/dv-processing/io/camera/usb_device.hpp"
#include "../../../include/dv-processing/io/camera/usb_device_nextgen.hpp"

#include <pybind11/pybind11.h>

namespace py = pybind11;

/**
 * @brief Bind io::Camera* base classes to python
 * @param m pybind The module to bind to
 */
void bind_camera_base(py::module &m) {
	py::enum_<dv::io::camera::CameraModel>(m, "CameraModel")
		.value("DVS128", dv::io::camera::CameraModel::DVS128)
		.value("DAVIS", dv::io::camera::CameraModel::DAVIS)
		.value("DVXPLORER", dv::io::camera::CameraModel::DVXPLORER)
		.value("DVXPLORER_M", dv::io::camera::CameraModel::DVXPLORER_M)
		.value("DVXPLORER_S", dv::io::camera::CameraModel::DVXPLORER_S)
		.value("DAVIS_GEN2", dv::io::camera::CameraModel::DAVIS_GEN2)
		.value("DVXPLORER_GEN2", dv::io::camera::CameraModel::DVXPLORER_GEN2);

	py::enum_<dv::io::camera::USBDeviceType>(m, "USBDeviceType")
		.value("FX2", dv::io::camera::USBDeviceType::FX2)
		.value("FX3_MB", dv::io::camera::USBDeviceType::FX3_MB)
		.value("FX3_BLUE", dv::io::camera::USBDeviceType::FX3_BLUE)
		.value("FX3_RED", dv::io::camera::USBDeviceType::FX3_RED)
		.value("CX3_MIPI", dv::io::camera::USBDeviceType::CX3_MIPI)
		.value("FX3_GEN2", dv::io::camera::USBDeviceType::FX3_GEN2);

	py::enum_<libusb_speed>(m, "libusb_speed")
		.value("LIBUSB_SPEED_UNKNOWN", libusb_speed::LIBUSB_SPEED_UNKNOWN)
		.value("LIBUSB_SPEED_LOW", libusb_speed::LIBUSB_SPEED_LOW)
		.value("LIBUSB_SPEED_FULL", libusb_speed::LIBUSB_SPEED_FULL)
		.value("LIBUSB_SPEED_HIGH", libusb_speed::LIBUSB_SPEED_HIGH)
		.value("LIBUSB_SPEED_SUPER", libusb_speed::LIBUSB_SPEED_SUPER)
		.value("LIBUSB_SPEED_SUPER_PLUS", libusb_speed::LIBUSB_SPEED_SUPER_PLUS)
	// SUPER_PLUS_X2 was introduced in libusb 1.0.28, but the LIBUSB_API_VERSION was not increased until 1.0.29.
	// This was a mistake on libusb side, so we work around this here by using the actual integer value.
#if (LIBUSB_API_VERSION >= DV_LIBUSB_VERSION_1_0_27_28)
		.value("LIBUSB_SPEED_SUPER_PLUS_X2",
			dv::IntegerAsEnum<libusb_speed>(6)) // libusb_speed::LIBUSB_SPEED_SUPER_PLUS_X2
#elif (LIBUSB_API_VERSION >= DV_LIBUSB_VERSION_1_0_29)
		.value("LIBUSB_SPEED_SUPER_PLUS_X2", libusb_speed::LIBUSB_SPEED_SUPER_PLUS_X2)
#endif
		;

	{
		// py::nodelete due to protected destructor of base-class USBDevice. It's never instantiated on its own!
		py::class_<dv::io::camera::USBDevice, std::unique_ptr<dv::io::camera::USBDevice, py::nodelete>> usbDevice(
			m, "USBDevice", "USB device support.");

		py::enum_<dv::io::camera::USBDevice::LogLevel>(usbDevice, "LogLevel")
			.value("LVL_NONE", dv::io::camera::USBDevice::LogLevel::LVL_NONE)
			.value("LVL_ERROR", dv::io::camera::USBDevice::LogLevel::LVL_ERROR)
			.value("LVL_WARNING", dv::io::camera::USBDevice::LogLevel::LVL_WARNING)
			.value("LVL_INFO", dv::io::camera::USBDevice::LogLevel::LVL_INFO)
			.value("LVL_DEBUG", dv::io::camera::USBDevice::LogLevel::LVL_DEBUG);

		py::class_<dv::io::camera::USBDevice::DeviceDescriptor> deviceDescriptor(usbDevice, "DeviceDescriptor");
		deviceDescriptor.def_readonly("serialNumber", &dv::io::camera::USBDevice::DeviceDescriptor::serialNumber)
			.def_readonly("serialNumber", &dv::io::camera::USBDevice::DeviceDescriptor::serialNumber)
			.def_readonly("pid", &dv::io::camera::USBDevice::DeviceDescriptor::pid)
			.def_readonly("deviceType", &dv::io::camera::USBDevice::DeviceDescriptor::deviceType)
			.def_readonly("cameraModel", &dv::io::camera::USBDevice::DeviceDescriptor::cameraModel)
			.def_readonly("busNumber", &dv::io::camera::USBDevice::DeviceDescriptor::busNumber)
			.def_readonly("devAddress", &dv::io::camera::USBDevice::DeviceDescriptor::devAddress)
			.def_readonly("firmwareVersion", &dv::io::camera::USBDevice::DeviceDescriptor::firmwareVersion);

		usbDevice
			.def("getDeviceDescriptor", &dv::io::camera::USBDevice::getDeviceDescriptor,
				"Get device descriptor. Describes this device uniquely.")
			.def("getFirmwareVersion", &dv::io::camera::USBDevice::getFirmwareVersion, "Get camera firmware version.")
			.def("getSerialNumber", &dv::io::camera::USBDevice::getSerialNumber, "Get camera serial number.")
			.def("getDeviceName", &dv::io::camera::USBDevice::getDeviceName, "Get camera name.")
			.def("getConnectionSpeed", &dv::io::camera::USBDevice::getConnectionSpeed, "Get USB connection speed.")
			.def("getPortPath", &dv::io::camera::USBDevice::getPortPath,
				"Get unique USB port path, consisting of bus number and port numbers.")
			.def("setLogLevel", &dv::io::camera::USBDevice::setLogLevel, py::arg("level"), "Set device's log-level.")
			.def("getLogLevel", &dv::io::camera::USBDevice::getLogLevel, "Get device's log-level.")
			.def("setTransfersNumber", &dv::io::camera::USBDevice::setTransfersNumber, py::arg("transfersNumber"),
				"Set number of USB buffers used for data transfer.")
			.def("getTransfersNumber", &dv::io::camera::USBDevice::getTransfersNumber,
				"Get number of USB buffers used for data transfer.")
			.def("setTransfersSize", &dv::io::camera::USBDevice::setTransfersSize, py::arg("transfersSize"),
				"Set size in bytes of USB buffers used for data transfer.")
			.def("getTransfersSize", &dv::io::camera::USBDevice::getTransfersSize,
				"Get size in bytes of USB buffers used for data transfer.");
	}

	py::class_<dv::io::camera::USBDeviceNextGen, dv::io::camera::USBDevice> usbDeviceNextGen(
		m, "USBDeviceNextGen", "USB device support for next-generation firmware.");

	{
		auto m_imu = m.def_submodule("imu");

		py::enum_<dv::io::camera::imu::ImuModel>(m_imu, "ImuModel")
			.value("IMU_NONE", dv::io::camera::imu::ImuModel::IMU_NONE)
			.value("IMU_INVENSENSE_6050_6150", dv::io::camera::imu::ImuModel::IMU_INVENSENSE_6050_6150)
			.value("IMU_INVENSENSE_6500_9250", dv::io::camera::imu::ImuModel::IMU_INVENSENSE_6500_9250)
			.value("IMU_BOSCH_BMI_160", dv::io::camera::imu::ImuModel::IMU_BOSCH_BMI_160)
			.value("IMU_BOSCH_BMI_270", dv::io::camera::imu::ImuModel::IMU_BOSCH_BMI_270);

		py::enum_<dv::io::camera::imu::InvensenseAccelRange>(m_imu, "InvensenseAccelRange")
			.value("RANGE_2G", dv::io::camera::imu::InvensenseAccelRange::RANGE_2G)
			.value("RANGE_4G", dv::io::camera::imu::InvensenseAccelRange::RANGE_4G)
			.value("RANGE_8G", dv::io::camera::imu::InvensenseAccelRange::RANGE_8G)
			.value("RANGE_16G", dv::io::camera::imu::InvensenseAccelRange::RANGE_16G);

		py::enum_<dv::io::camera::imu::InvensenseGyroRange>(m_imu, "InvensenseGyroRange")
			.value("RANGE_250DPS", dv::io::camera::imu::InvensenseGyroRange::RANGE_250DPS)
			.value("RANGE_500DPS", dv::io::camera::imu::InvensenseGyroRange::RANGE_500DPS)
			.value("RANGE_1000DPS", dv::io::camera::imu::InvensenseGyroRange::RANGE_1000DPS)
			.value("RANGE_2000DPS", dv::io::camera::imu::InvensenseGyroRange::RANGE_2000DPS);

		py::enum_<dv::io::camera::imu::BoschBMI160AccelDataRate>(m_imu, "BoschBMI160AccelDataRate")
			.value("RATE_12_5HZ", dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_12_5HZ)
			.value("RATE_25HZ", dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_25HZ)
			.value("RATE_50HZ", dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_50HZ)
			.value("RATE_100HZ", dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_100HZ)
			.value("RATE_200HZ", dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_200HZ)
			.value("RATE_400HZ", dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_400HZ)
			.value("RATE_800HZ", dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_800HZ)
			.value("RATE_1600HZ", dv::io::camera::imu::BoschBMI160AccelDataRate::RATE_1600HZ);

		py::enum_<dv::io::camera::imu::BoschBMI160AccelRange>(m_imu, "BoschBMI160AccelRange")
			.value("RANGE_2G", dv::io::camera::imu::BoschBMI160AccelRange::RANGE_2G)
			.value("RANGE_4G", dv::io::camera::imu::BoschBMI160AccelRange::RANGE_4G)
			.value("RANGE_8G", dv::io::camera::imu::BoschBMI160AccelRange::RANGE_8G)
			.value("RANGE_16G", dv::io::camera::imu::BoschBMI160AccelRange::RANGE_16G);

		py::enum_<dv::io::camera::imu::BoschBMI160AccelFilter>(m_imu, "BoschBMI160AccelFilter")
			.value("FILTER_NORMAL", dv::io::camera::imu::BoschBMI160AccelFilter::FILTER_NORMAL)
			.value("FILTER_OSR2", dv::io::camera::imu::BoschBMI160AccelFilter::FILTER_OSR2)
			.value("FILTER_OSR4", dv::io::camera::imu::BoschBMI160AccelFilter::FILTER_OSR4);

		py::enum_<dv::io::camera::imu::BoschBMI160GyroDataRate>(m_imu, "BoschBMI160GyroDataRate")
			.value("RATE_25HZ", dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_25HZ)
			.value("RATE_50HZ", dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_50HZ)
			.value("RATE_100HZ", dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_100HZ)
			.value("RATE_200HZ", dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_200HZ)
			.value("RATE_400HZ", dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_400HZ)
			.value("RATE_800HZ", dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_800HZ)
			.value("RATE_1600HZ", dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_1600HZ)
			.value("RATE_3200HZ", dv::io::camera::imu::BoschBMI160GyroDataRate::RATE_3200HZ);

		py::enum_<dv::io::camera::imu::BoschBMI160GyroRange>(m_imu, "BoschBMI160GyroRange")
			.value("RANGE_125DPS", dv::io::camera::imu::BoschBMI160GyroRange::RANGE_125DPS)
			.value("RANGE_250DPS", dv::io::camera::imu::BoschBMI160GyroRange::RANGE_250DPS)
			.value("RANGE_500DPS", dv::io::camera::imu::BoschBMI160GyroRange::RANGE_500DPS)
			.value("RANGE_1000DPS", dv::io::camera::imu::BoschBMI160GyroRange::RANGE_1000DPS)
			.value("RANGE_2000DPS", dv::io::camera::imu::BoschBMI160GyroRange::RANGE_2000DPS);

		py::enum_<dv::io::camera::imu::BoschBMI160GyroFilter>(m_imu, "BoschBMI160GyroFilter")
			.value("FILTER_NORMAL", dv::io::camera::imu::BoschBMI160GyroFilter::FILTER_NORMAL)
			.value("FILTER_OSR2", dv::io::camera::imu::BoschBMI160GyroFilter::FILTER_OSR2)
			.value("FILTER_OSR4", dv::io::camera::imu::BoschBMI160GyroFilter::FILTER_OSR4);
	}

	py::class_<dv::io::camera::CameraInputBase, dv::io::InputBase> cameraInputBase(
		m, "CameraInputBase", "Base class for all cameras.");

	py::enum_<dv::io::camera::CameraInputBase::Flatten>(cameraInputBase, "Flatten")
		.value("NONE", dv::io::camera::CameraInputBase::Flatten::NONE)
		.value("FLATTEN_ON", dv::io::camera::CameraInputBase::Flatten::FLATTEN_ON)
		.value("ON_ONLY", dv::io::camera::CameraInputBase::Flatten::ON_ONLY)
		.value("OFF_ONLY", dv::io::camera::CameraInputBase::Flatten::OFF_ONLY);

	cameraInputBase
		.def("getFlipHorizontal", &dv::io::camera::CameraInputBase::getFlipHorizontal,
			"Status of horizontal events flip.")
		.def("setFlipHorizontal", &dv::io::camera::CameraInputBase::setFlipHorizontal, py::arg("flipHorizontalEvents"),
			"Flip events horizontally.")
		.def("getFlipVertical", &dv::io::camera::CameraInputBase::getFlipVertical, "Status of vertical events flip.")
		.def("setFlipVertical", &dv::io::camera::CameraInputBase::setFlipVertical, py::arg("flipVerticalEvents"),
			"Flip events vertically.")
		.def("getFlatten", &dv::io::camera::CameraInputBase::getFlatten, "Status of event polarity flattening.")
		.def("setFlatten", &dv::io::camera::CameraInputBase::setFlatten, py::arg("flattenEvents"),
			"Flatten events polarity.")
		.def("getCropArea", &dv::io::camera::CameraInputBase::getCropArea, "Get events Region of Interest (ROI).")
		.def("setCropArea", &dv::io::camera::CameraInputBase::setCropArea, py::arg("cropAreaEvents"),
			"Set events Region of Interest (ROI).")
		.def("getImuModel", &dv::io::camera::CameraInputBase::getImuModel, "Return IMU model used on device.")
		.def("getPixelPitch", &dv::io::camera::CameraInputBase::getPixelPitch, "Return pixel pitch distance in meters.")
		.def("getTimeInterval", &dv::io::camera::CameraInputBase::getTimeInterval,
			"Get the time interval for data commit.")
		.def("setTimeInterval", &dv::io::camera::CameraInputBase::setTimeInterval, py::arg("timeInterval"),
			"Set a new time interval value for data commit.")
		.def("getTimestampOffset", &dv::io::camera::CameraInputBase::getTimestampOffset, "Get the timestamp offset.")
		.def("getNextEventPacket", &dv::io::camera::CameraInputBase::getNextEventPacket,
			"Parse and retrieve next event packet (internal format).")
		.def("getEventSeekTime", &dv::io::camera::CameraInputBase::getEventSeekTime,
			"Get latest timestamp of event data stream that has been read from the capture class.")
		.def("getFrameSeekTime", &dv::io::camera::CameraInputBase::getFrameSeekTime,
			"Get latest timestamp of frame data stream that has been read from the capture class.")
		.def("getImuSeekTime", &dv::io::camera::CameraInputBase::getImuSeekTime,
			"Get latest timestamp of IMU data stream that has been read from the capture class.")
		.def("getTriggerSeekTime", &dv::io::camera::CameraInputBase::getTriggerSeekTime,
			"Get latest timestamp of triggers data stream that has been read from the capture class.")
		.def("readNext", &dv::io::camera::CameraInputBase::readNext,
			"Read a packet from the camera and return a variant of any packet. You can use std::visit with "
			"`dv::io::DataReadHandler` to handle each type of packet using callback methods. This method might not "
			"maintain timestamp monotonicity between different stream types.");

	py::class_<dv::io::camera::SyncCameraInputBase, dv::io::camera::CameraInputBase> syncCameraInputBase(
		m, "SyncCameraInputBase", "Base class for all cameras with time synchronization support.");

	syncCameraInputBase
		.def("isMaster", &dv::io::camera::SyncCameraInputBase::isMaster,
			"Report if this camera is a clock synchronization master.")
		.def("isSynchronized", &dv::io::camera::SyncCameraInputBase::isSynchronized,
			"Report if this camera is properly synchronized with others in a multi-camera scenario.")
		.def("synchronizeWith",
			py::overload_cast<dv::io::camera::SyncCameraInputBase &>(
				&dv::io::camera::SyncCameraInputBase::synchronizeWith),
			py::arg("secondary"),
			"Synchronize this camera with another. This camera is expected to be the clock synchronization master.")
		.def("synchronizeWith",
			py::overload_cast<dv::io::camera::SyncCameraInputBase *>(
				&dv::io::camera::SyncCameraInputBase::synchronizeWith),
			py::arg("secondary"),
			"Synchronize this camera with another. This camera is expected to be the clock synchronization master.")
		.def("synchronizeWith",
			py::overload_cast<const std::span<dv::io::camera::SyncCameraInputBase *>>(
				&dv::io::camera::SyncCameraInputBase::synchronizeWith),
			py::arg("secondaryCameras"),
			"Synchronize this camera with multiple others. This camera is expected to be the clock synchronization "
			"master.");

	m.def("synchronizeAny", &dv::io::camera::synchronizeAny, py::arg("cameras"),
		"Synchronize any number of cameras with each-other.");

	m.def("synchronizeAnyTwo",
		py::overload_cast<dv::io::camera::SyncCameraInputBase *, dv::io::camera::SyncCameraInputBase *>(
			&dv::io::camera::synchronizeAnyTwo),
		py::arg("first"), py::arg("second"), "Synchronize two cameras with each-other.");
	m.def("synchronizeAnyTwo",
		py::overload_cast<dv::io::camera::SyncCameraInputBase &, dv::io::camera::SyncCameraInputBase &>(
			&dv::io::camera::synchronizeAnyTwo),
		py::arg("first"), py::arg("second"), "Synchronize two cameras with each-other.");
}
