#pragma once

#include "../../../include/dv-processing/core/multi_stream_slicer.hpp"

#include <pybind11/pybind11.h>

namespace py = pybind11;

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

	py::class_<dv::MultiStreamSlicer<dv::EventStore>> multiStreamSlicer(m, "EventMultiStreamSlicer",
		"MultiStreamSlicer takes multiple stream of timestamped data, slices data with configured intervals and calls "
		"a given callback method on each interval. It is an extension of StreamSlicer class that can synchronously "
		"slice multiple streams. Each stream has to be named uniquely, the name is carried over to the callback method "
		"to identify each stream. The class is relies heavily on templating, so it supports different containers of "
		"data, as long as the container is an iterable and each element contains an accessible timestamp in "
		"microsecond format. The slicing is driven by the main stream, which needs to be specified during construction "
		"time, a type of the main stream is the first template argument and the name for the main stream is provided "
		"as the constructor first argument. By default, four types are supported without additional configuration: "
		"dv::EventStore, dv::TriggerPacket, dv::IMUPacket. Additional types can be added by specifying "
		"them as additional template parameters.");

	py::enum_<dv::TimeSlicingApproach>(m, "TimeSlicingApproach")
		.value("BACKWARD", dv::TimeSlicingApproach::BACKWARD)
		.value("FORWARD", dv::TimeSlicingApproach::FORWARD);

	py::class_<dv::MultiStreamSlicer<dv::EventStore>::MapOfVariants>(multiStreamSlicer, "MapOfVariants")
		.def(
			"getEvents",
			[](dv::MultiStreamSlicer<dv::EventStore>::MapOfVariants &self, const std::string &streamName) {
				return self.template get<dv::EventStore>(streamName);
			},
			"streamName"_a, "Get events of a given stream name")
		.def(
			"getImu",
			[](dv::MultiStreamSlicer<dv::EventStore>::MapOfVariants &self, const std::string &streamName) {
				return self.template get<dv::IMUPacket>(streamName);
			},
			"streamName"_a, "Get imu of a given stream name")
		.def(
			"getTriggers",
			[](dv::MultiStreamSlicer<dv::EventStore>::MapOfVariants &self, const std::string &streamName) {
				return self.template get<dv::TriggerPacket>(streamName);
			},
			"streamName"_a, "Get triggers of a given stream name")
		.def(
			"getFrames",
			[](dv::MultiStreamSlicer<dv::EventStore>::MapOfVariants &self, const std::string &streamName) {
				return self.template get<std::vector<dv::Frame>>(streamName);
			},
			"streamName"_a, "Get frames of a given stream name");

	multiStreamSlicer
		.def(py::init<const std::string &>(), "streamName"_a,
			"Initialize a multi-stream slicer by providing a name for main stream")
		.def(
			"accept",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const std::string &streamName,
				const dv::EventStore &events) {
				self.accept(streamName, events);
			},
			"streamName"_a, "events"_a, "Pass data of a stream into the slicer")
		.def(
			"accept",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const std::string &streamName, const dv::IMUPacket &imu) {
				self.accept(streamName, imu);
			},
			"streamName"_a, "imu"_a, "Pass data of a stream into the slicer")
		.def(
			"accept",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const std::string &streamName,
				const dv::TriggerPacket &triggers) {
				self.accept(streamName, triggers);
			},
			"streamName"_a, "triggers"_a, "Pass data of a stream into the slicer")
		.def(
			"accept",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const std::string &streamName,
				const std::vector<dv::Frame> &frames) {
				self.accept(streamName, frames);
			},
			"streamName"_a, "frames"_a, "Pass data of a stream into the slicer")
		.def(
			"addEventStream",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const std::string &streamName) {
				self.addStream<dv::EventStore>(streamName);
			},
			"streamName"_a, "Add an event stream to the slicer.")
		.def(
			"addImuStream",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const std::string &streamName) {
				self.addStream<dv::IMUPacket>(streamName);
			},
			"streamName"_a, "Add an imu stream to the slicer.")
		.def(
			"addTriggerStream",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const std::string &streamName) {
				self.addStream<dv::TriggerPacket>(streamName);
			},
			"streamName"_a, "Add a trigger stream to the slicer.")
		.def(
			"addFrameStream",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const std::string &streamName) {
				self.addStream<std::vector<dv::Frame>>(streamName);
			},
			"streamName"_a, "Add a frame stream to the slicer.")
		.def(
			"doEveryTimeInterval",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const dv::Duration interval,
				std::function<void(const dv::MultiStreamSlicer<dv::EventStore>::MapOfVariants &)> callback) {
				return self.doEveryTimeInterval(interval, std::move(callback));
			},
			"interval"_a, "callback"_a,
			"Register a callback to be performed at a given interval. Data is passed as an argument to the method.")
		.def(
			"modifyTimeInterval",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const int jobId, const dv::Duration timeInterval) {
				self.modifyTimeInterval(jobId, timeInterval);
			},
			"jobId"_a, "timeInterval"_a, "Modify the execution interval of a job.")
		.def(
			"doEveryNumberOfElements",
			[](dv::MultiStreamSlicer<dv::EventStore> &self, const size_t n,
				std::function<void(const dv::MultiStreamSlicer<dv::EventStore>::MapOfVariants &)> callback,
				const dv::TimeSlicingApproach sliceTiming) {
				return self.doEveryNumberOfElements(n, std::move(callback), sliceTiming);
			},
			"n"_a, "callback"_a, "sliceTiming"_a = dv::TimeSlicingApproach::BACKWARD,
			"Register a callback to be performed every number of elements. Data is passed as an argument to the "
			"method.")
		.def("modifyNumberInterval", &dv::MultiStreamSlicer<dv::EventStore>::modifyNumberInterval, "jobId"_a, "n"_a,
			"Modify the number of elements to slice for a job.")
		.def("hasJob", &dv::MultiStreamSlicer<dv::EventStore>::hasJob, "jobId"_a,
			"Returns true if the slicer contains the slice-job with the provided id.")
		.def("removeJob", &dv::MultiStreamSlicer<dv::EventStore>::removeJob, "jobId"_a,
			"Removes the given job from the list of current jobs.");
}
