#pragma once

#include "../../../include/dv-processing/io/mono_camera_writer.hpp"

#include <pybind11/pybind11.h>

namespace py = pybind11;

/**
 * @brief Bind io::MonoCameraWriter class to python
 * @param m pybind The module to bind to
 */
void bind_mono_camera_writer(py::module &m) {
	using pybind11::operator""_a;

	py::class_<dv::io::MonoCameraWriter> monoCameraWriter(m, "MonoCameraWriter", "Create an aedat4 file writer class.");

	py::class_<dv::io::MonoCameraWriter::Config>(
		monoCameraWriter, "Config", "A configuration structure for the MonoCameraWriter.")
		.def(py::init<const std::string &, dv::CompressionType>(), "cameraName"_a,
			"compression"_a = dv::CompressionType::LZ4)
		.def_readwrite("compression", &dv::io::MonoCameraWriter::Config::compression)
		.def_readwrite("cameraName", &dv::io::MonoCameraWriter::Config::cameraName)
		.def("addEventStream", &dv::io::MonoCameraWriter::Config::addEventStream, "resolution"_a,
			"streamName"_a = "events", "source"_a = std::nullopt, "Add an event stream with a given resolution.")
		.def("addFrameStream", &dv::io::MonoCameraWriter::Config::addFrameStream, "resolution"_a,
			"streamName"_a = "frames", "source"_a = std::nullopt, "Add a frame stream with a given resolution.")
		.def("addImuStream", &dv::io::MonoCameraWriter::Config::addImuStream, "streamName"_a = "imu",
			"source"_a = std::nullopt, "Add an IMU data stream.")
		.def("addTriggerStream", &dv::io::MonoCameraWriter::Config::addTriggerStream, "streamName"_a = "triggers",
			"source"_a = std::nullopt, "Add a trigger stream.")
		.def("addPoseStream", &dv::io::MonoCameraWriter::Config::addStream<dv::Pose>, "streamName"_a = "poses",
			"source"_a = std::nullopt, "Add a pose stream.")
		.def("addTimedKeyPointStream", &dv::io::MonoCameraWriter::Config::addStream<dv::TimedKeyPointPacket>,
			"streamName"_a = "timedKeyPoints", "source"_a = std::nullopt, "Add a timed key point stream.")
		.def("addBoundingBoxStream", &dv::io::MonoCameraWriter::Config::addStream<dv::BoundingBoxPacket>,
			"streamName"_a = "boundingBoxes", "source"_a = std::nullopt, "Add a bounding box stream.")
		.def("addStreamMetadata", &dv::io::MonoCameraWriter::Config::addStreamMetadata)
		.def("findStreamResolution", &dv::io::MonoCameraWriter::Config::findStreamResolution, "streamName"_a,
			"Parse resolution of the stream from metadata of the stream. Resolution should be set as two metadata "
			"parameters: \"sizeX\" and \"sizeY\" parameters.");

	monoCameraWriter
		.def(py::init<const std::filesystem::path &, const dv::io::MonoCameraWriter::Config &>(), "aedat4Path"_a,
			"config"_a, "Create an aedat4 file writer with simplified API.")
		.def(py::init<const std::filesystem::path &, const dv::io::camera::CameraInputBase &, dv::CompressionType>(),
			"aedat4Path"_a, "capture"_a, "compression"_a = dv::CompressionType::LZ4,
			"Create an aedat4 file writer with output streams available in camera capture class.")
		.def_static("EventOnlyConfig", &dv::io::MonoCameraWriter::EventOnlyConfig, py::arg("cameraName"),
			py::arg("resolution"), py::arg("compression") = dv::CompressionType::LZ4,
			"Generate a config for a writer that will expect a stream of events only.")
		.def_static("FrameOnlyConfig", &dv::io::MonoCameraWriter::FrameOnlyConfig, py::arg("cameraName"),
			py::arg("resolution"), py::arg("compression") = dv::CompressionType::LZ4,
			"Generate a config for a writer that will expect a stream of frames only.")
		.def_static("DVSConfig", &dv::io::MonoCameraWriter::DVSConfig, py::arg("cameraName"), py::arg("resolution"),
			py::arg("compression") = dv::CompressionType::LZ4,
			"Generate a config for a writer that will expect data from a DVS camera - events, IMU, triggers.")
		.def_static("DAVISConfig", &dv::io::MonoCameraWriter::DAVISConfig, py::arg("cameraName"), py::arg("resolution"),
			py::arg("compression") = dv::CompressionType::LZ4,
			"Generate a config for a writer that will expect data from a DAVIS camera - frames, events, IMU, triggers.")
		.def_static("CaptureConfig", &dv::io::MonoCameraWriter::CaptureConfig, py::arg("capture"),
			py::arg("compression") = dv::CompressionType::LZ4,
			"Generate a config for a given capture device, performs capability inspection.")
		.def("writeEvents",
			py::overload_cast<const dv::EventStore &, const std::string &>(&dv::io::MonoCameraWriter::writeEvents),
			"eventStore"_a, "streamName"_a = "events",
			"Write an event store into the output file. The store is written by maintaining internal data partial "
			"ordering and fragmentation. The data is passed directly into the serialization procedure without "
			"performing copies. Data is serialized and the actual file IO is performed on a separate thread.")
		.def("writeFrame",
			py::overload_cast<const dv::Frame &, const std::string &>(&dv::io::MonoCameraWriter::writeFrame), "frame"_a,
			"streamName"_a = "frames",
			"Write a frame image into the file. The data is passed directly into the serialization procedure without "
			"performing copies. Data is serialized and the actual file IO is performed on a separate thread.")
		.def("writeImu", py::overload_cast<const dv::IMU &, const std::string &>(&dv::io::MonoCameraWriter::writeImu),
			"imu"_a, "streamName"_a = "imu",
			"Write an IMU measurement. This function is not immediate, it batches the measurements until a configured "
			"amount is reached, only then the data is passed to the serialization step. Only then the data will be "
			"passed to the file write IO thread. If the file is closed (the object gets destroyed), destructor will "
			"dump the rest of the buffered measurements to the serialization step.")
		.def("writeTrigger",
			py::overload_cast<const dv::Trigger &, const std::string &>(&dv::io::MonoCameraWriter::writeTrigger),
			"trigger"_a, "streamName"_a = "triggers",
			"Write a trigger measurement. This function is not immediate, it batches the measurements until a "
			"configured amount is reached, only then the data is passed to the serialization step. Only then the data "
			"will be passed to the file write IO thread. If the file is closed (the object gets destroyed), destructor "
			"will dump the rest of the buffered measurements to the serialization step.")
		.def("writePose", &dv::io::MonoCameraWriter::writePacket<dv::Pose>, "pose"_a, "streamName"_a = "poses",
			"Write a pose into the file. The data is passed directly into the serialization procedure without "
			"performing copies. Data is serialized and the actual file IO is performed on a separate thread.")
		.def("writeTimedKeyPoint",
			&dv::io::MonoCameraWriter::writePacketElement<dv::TimedKeyPointPacket, dv::TimedKeyPoint>,
			"timedKeyPoint"_a, "streamName"_a = "timedKeyPoints",
			"Write a timed key point measurement. This function is not immediate, it batches the measurements until a "
			"configured amount is reached, only then the data is passed to the serialization step. Only then the data "
			"will be passed to the file write IO thread. If the file is closed (the object gets destroyed), destructor "
			"will dump the rest of the buffered measurements to the serialization step.")
		.def("writeBoundingBox", &dv::io::MonoCameraWriter::writePacketElement<dv::BoundingBoxPacket, dv::BoundingBox>,
			"boundingBox"_a, "streamName"_a = "boundingBoxes",
			"Write a bounding box. This function is not immediate, it batches the measurements until a "
			"configured amount is reached, only then the data is passed to the serialization step. Only then the data "
			"will be passed to the file write IO thread. If the file is closed (the object gets destroyed), destructor "
			"will dump the rest of the buffered measurements to the serialization step.")
		.def("writeEventPacket", &dv::io::MonoCameraWriter::writeEventPacket, "eventPacket"_a,
			"streamName"_a = "events",
			"Write an event packet into the output file. The data is passed directly into the serialization procedure "
			"without performing copies. Data is serialized and the actual file IO is performed on a separate thread.")
		.def("writeImuPacket", &dv::io::MonoCameraWriter::writeImuPacket, "imuPacket"_a, "streamName"_a = "imu",
			"Write a packet of IMU measurement. The data is passed directly into the serialization procedure "
			"without performing copies. Data is serialized and the actual file IO is performed on a separate thread.")
		.def("writeTriggerPacket", &dv::io::MonoCameraWriter::writeTriggerPacket, "triggerPacket"_a,
			"streamName"_a = "triggers",
			"Write a packet of trigger measurement. The data is passed directly into the serialization procedure "
			"without performing copies. Data is serialized and the actual file IO is performed on a separate thread.")
		.def("writeTimedKeyPointPacket", &dv::io::MonoCameraWriter::writePacket<dv::TimedKeyPointPacket>,
			"timedKeyPoints"_a, "streamName"_a = "timedKeyPoints",
			"Write timed keypoint packet into the file. The data is passed directly into the serialization procedure "
			"without performing copies. Data is serialized and the actual file IO is performed on a separate thread.")
		.def("writeBoundingBoxesPacket", &dv::io::MonoCameraWriter::writePacket<dv::BoundingBoxPacket>,
			"boundingBoxes"_a, "streamName"_a = "boundingBoxes",
			"Write bounding box packet into the file. The data is passed directly into the serialization procedure "
			"without performing copies. Data is serialized and the actual file IO is performed on a separate thread.")
		.def("setPackagingCount", &dv::io::MonoCameraWriter::setPackagingCount, "packagingCount"_a,
			"Set the size batch size for trigger and imu buffering. The single measurements passed into `writeTrigger` "
			"and `writeImu` functions will packed into batches of the given size before writing to the file. A "
			"packaging value of 0 or 1 will cause each measurement to be serialized immediately.")
		.def("isEventStreamConfigured", &dv::io::MonoCameraWriter::isEventStreamConfigured, "streamName"_a = "events",
			"Check if a event stream is configured for this writer.")
		.def("isFrameStreamConfigured", &dv::io::MonoCameraWriter::isFrameStreamConfigured, "streamName"_a = "frames",
			"Check if a frame stream is configured for this writer.")
		.def("isImuStreamConfigured", &dv::io::MonoCameraWriter::isImuStreamConfigured, "streamName"_a = "imu",
			"Check if a IMU stream is configured for this writer.")
		.def("isTriggerStreamConfigured", &dv::io::MonoCameraWriter::isTriggerStreamConfigured,
			"streamName"_a = "triggers", "Check if a trigger stream is configured for this writer.")
		.def("isPoseStreamConfigured", &dv::io::MonoCameraWriter::isStreamConfigured<dv::Pose>,
			"streamName"_a = "poses", "Check if a pose stream is configured for this writer.")
		.def("isTimedKeyPointStreamConfigured", &dv::io::MonoCameraWriter::isStreamConfigured<dv::TimedKeyPointPacket>,
			"streamName"_a = "timedKeyPoint", "Check if a timed key point stream is configured for this writer.")
		.def("isBoundingBoxesStreamConfigured", &dv::io::MonoCameraWriter::isStreamConfigured<dv::BoundingBoxPacket>,
			"streamName"_a = "boundingBoxes", "Check if a bounding box stream is configured for this writer.");
}
