#pragma once

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

#include <pybind11/pybind11.h>

namespace py = pybind11;

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

	py::class_<dv::io::NetworkWriter>(
		m, "NetworkWriter", "Network server class for streaming AEDAT4 serialized data types.")
		.def(py::init([](const std::string_view ip, const uint16_t port, const dv::io::Stream &stream,
						  const size_t maxClientConnections, std::function<void(int32_t, std::string_view)> callback) {
			return std::make_unique<dv::io::NetworkWriter>(ip, port, stream, maxClientConnections,
				[callback = std::move(callback)](
					const boost::system::error_code &error, const std::string_view message) {
					callback(error.value(), message);
				});
		}),
			"ipAddress"_a, "port"_a, "stream"_a, "maxClientConnections"_a = 10,
			"errorMessageCallback"_a = std::function([](const int32_t, const std::string_view) {
			}),
			"Create a non-encrypted server that listens for connections on a given IP address. Supports multiple "
			"clients.")
		.def(py::init([](const std::filesystem::path &socketPath, const dv::io::Stream &stream,
						  const size_t maxClientConnections, std::function<void(int32_t, std::string_view)> callback) {
			return std::make_unique<dv::io::NetworkWriter>(socketPath, stream, maxClientConnections,
				[callback = std::move(callback)](
					const boost::system::error_code &error, const std::string_view message) {
					callback(error.value(), std::string(message));
				});
		}),
			"socketPath"_a, "stream"_a, "maxClientConnections"_a = 10,
			"errorMessageCallback"_a = std::function([](const int32_t, const std::string_view) {
			}),
			"Create a local socket server. Provide a path to the socket, if a file already exists on a given path, the "
			"connection will fail by throwing an exception. It is required that the given socket path does not point "
			"to an existing socket file. If the file *can* exist, it is up to the user of this class to decide whether "
			"it is safe to remove any existing socket files or the class should not bind to the path.")
		.def(py::init([](const std::string_view ipAddress, const uint16_t port, const dv::io::Stream &stream,
						  const std::filesystem::path &certificateChain, const std::filesystem::path &privateKey,
						  const std::filesystem::path &CAFile, const size_t maxClientConnections,
						  std::function<void(int32_t, std::string_view)> callback) {
			return std::make_unique<dv::io::NetworkWriter>(ipAddress, port, stream,
				dv::io::encrypt::defaultEncryptionServer(certificateChain, privateKey, CAFile), maxClientConnections,
				[callback = std::move(callback)](
					const boost::system::error_code &error, const std::string_view message) {
					callback(error.value(), std::string(message));
				});
		}),
			"ipAddress"_a, "port"_a, "stream"_a, "certificateChain"_a, "privateKey"_a, "CAFile"_a,
			"maxClientConnections"_a = 10,
			"errorMessageCallback"_a = std::function([](const int32_t, const std::string_view) {
			}),
			"Create an encrypted server that listens for connections on a given IP address. Supports multiple clients. "
			"Provide server certificate chain, private key, and CAFile for encrypted connection and client(s) "
			"certificate verification.")
		.def("writeEvents", &dv::io::NetworkWriter::writeEvents, "events"_a,
			"Write an event store to the network stream.")
		.def("writeFrame", &dv::io::NetworkWriter::writeFrame, "frame"_a, "Write a frame image to the network stream.")
		.def("writeImu", &dv::io::NetworkWriter::writeImu, "imu"_a, "Write IMU data to the socket.")
		.def("writeTriggers", &dv::io::NetworkWriter::writeTriggers, "triggers"_a,
			"Write trigger data to the network stream.")
		.def(
			"writePose",
			[](dv::io::NetworkWriter &self, const dv::Pose &pose) {
				self.writePacket(pose);
			},
			"pose"_a, "Write pose data")
		.def(
			"writeBoundingBox",
			[](dv::io::NetworkWriter &self, const dv::BoundingBoxPacket &boundingBox) {
				self.writePacket(boundingBox);
			},
			"boundingBox"_a, "Write bounding boxes")
		.def(
			"writeTimedKeyPoints",
			[](dv::io::NetworkWriter &self, const dv::TimedKeyPointPacket &timedKeyPoints) {
				self.writePacket(timedKeyPoints);
			},
			"timedKeyPoints"_a, "Write timed key points")
		.def("getCameraName", &dv::io::NetworkWriter::getCameraName,
			"Get camera name. It is looked up from the stream definition during construction.")
		.def("getQueuedPacketCount", &dv::io::NetworkWriter::getQueuedPacketCount,
			"Get number of packets in the write queue.")
		.def("getClientCount", &dv::io::NetworkWriter::getClientCount, "Get number of active connected clients.");
}
