#pragma once

#include "../../../include/dv-processing/features/event_feature_lk_tracker.hpp"

#include <pybind11/pybind11.h>

namespace py = pybind11;

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

	py::class_<dv::features::EventFeatureLKTracker<>>(m, "EventFeatureLKTracker",
		"Event-based Lucas-Kanade tracker, the tracking is achieved by accumulating frames and running the "
		"classic LK "
		"frame based tracker on them.")
		.def_static(
			"RegularTracker",
			[](const cv::Size &resolution, const dv::features::LucasKanadeConfig &config) {
				return dv::features::EventFeatureLKTracker<>::RegularTracker(resolution, config).release();
			},
			py::arg("resolution"), py::arg("config") = dv::features::LucasKanadeConfig(),
			"Create a tracker instance that performs tracking of features on event accumulated frames. Features are "
			"detected and tracked on event accumulated frames.")
		.def_static(
			"MotionAwareTracker",
			[](const dv::camera::CameraGeometry::SharedPtr &camera, const dv::features::LucasKanadeConfig &config) {
				return dv::features::EventFeatureLKTracker<>::MotionAwareTracker(camera, config).release();
			},
			py::arg("camera"), py::arg("config") = dv::features::LucasKanadeConfig(),
			"Create a tracker instance that performs tracking of features on event accumulated frames. Features are "
			"detected and tracked on event accumulated frames. Additionally, camera motion and scene depth are used to "
			"generate motion compensated frames, which are way sharper than usual accumulated frames. This requires "
			"camera sensor to be calibrated.")
		.def(
			"accept",
			[](dv::features::EventFeatureLKTracker<> &self, const dv::EventStore &events) {
				self.accept(events);
			},
			py::arg("events"),
			"Add the input events. Since the batch of events might contain information for more than a single tracking "
			"iteration configurable by the framerate parameter, the tracking function should be executed on a loop "
			"until it returns a null-pointer, signifying end of available data processing")
		.def(
			"accept",
			[](dv::features::EventFeatureLKTracker<> &self, const dv::measurements::Depth &depth) {
				self.accept(depth);
			},
			py::arg("depth"), "Add scene depth, a median depth value of tracked landmarks usually works well enough.")
		.def(
			"accept",
			[](dv::features::EventFeatureLKTracker<> &self, const dv::kinematics::Transformationf &transformation) {
				self.accept(transformation);
			},
			py::arg("transformation"),
			"Add camera transformation, usually in the world coordinate frame (`T_WC`). Although the class only "
			"extract the motion difference, so any other reference frame should also work as long as reference frames "
			"are not mixed up.")
		.def(
			"setRedetectionStrategy",
			[](dv::features::EventFeatureLKTracker<> &self, const dv::features::NoRedetection &strategy) {
				self.setRedetectionStrategy(std::make_unique<dv::features::NoRedetection>(strategy));
			},
			py::arg("strategy"), "Set a new redetection strategy.")
		.def(
			"setRedetectionStrategy",
			[](dv::features::EventFeatureLKTracker<> &self, const dv::features::FeatureCountRedetection &strategy) {
				self.setRedetectionStrategy(std::make_unique<dv::features::FeatureCountRedetection>(strategy));
			},
			py::arg("strategy"), "Set a new redetection strategy.")
		.def(
			"setRedetectionStrategy",
			[](dv::features::EventFeatureLKTracker<> &self, const dv::features::UpdateIntervalRedetection &strategy) {
				self.setRedetectionStrategy(std::make_unique<dv::features::UpdateIntervalRedetection>(strategy));
			},
			py::arg("strategy"), "Set a new redetection strategy.")
		.def(
			"setRedetectionStrategy",
			[](dv::features::EventFeatureLKTracker<> &self,
				const dv::features::UpdateIntervalOrFeatureCountRedetection &strategy) {
				self.setRedetectionStrategy(
					std::make_unique<dv::features::UpdateIntervalOrFeatureCountRedetection>(strategy));
			},
			py::arg("strategy"), "Set a new redetection strategy.")
		.def(
			"setDetector",
			[](dv::features::EventFeatureLKTracker<> &self, const dv::features::ImagePyrFeatureDetector &detector) {
				self.setDetector(std::make_unique<dv::features::ImagePyrFeatureDetector>(detector));
			},
			py::arg("detector"), "Set a new feature (corner) detector.")
		.def(
			"setMotionPredictor",
			[](dv::features::EventFeatureLKTracker<> &self, const dv::kinematics::PixelMotionPredictor &predictor) {
				self.setMotionPredictor(std::make_unique<dv::kinematics::PixelMotionPredictor>(predictor));
			},
			py::arg("predictor"),
			"Set new pixel motion predictor instance. Warning: motion prediction requires camera calibration to "
			"be set, otherwise the function will not instantiate the motion predictor.")
		.def(
			"setAccumulator",
			[](dv::features::EventFeatureLKTracker<> &self, const dv::EdgeMapAccumulator &accumulator) {
				self.setAccumulator(std::make_unique<dv::EdgeMapAccumulator>(accumulator));
			},
			py::arg("accumulator"), "Set an accumulator instance to be used for frame generation.")
		.def("getAccumulatedFrame", &dv::features::EventFeatureLKTracker<>::getAccumulatedFrame,
			"Get the latest accumulated frame.")
		.def("getMaxTracks", &dv::features::EventFeatureLKTracker<>::getMaxTracks, "Get the maximum number of tracks.")
		.def("setMaxTracks", &dv::features::EventFeatureLKTracker<>::setMaxTracks, py::arg("maxTracks"))
		.def("getFramerate", &dv::features::EventFeatureLKTracker<>::getFramerate, "Get configured framerate.")
		.def("setFramerate", &dv::features::EventFeatureLKTracker<>::setFramerate, py::arg("framerate"),
			"Set the accumulation and tracking framerate.")
		.def("getStoreTimeLimit", &dv::features::EventFeatureLKTracker<>::getStoreTimeLimit,
			"Get the event storage time limit.")
		.def("setStoreTimeLimit", &dv::features::EventFeatureLKTracker<>::setStoreTimeLimit, py::arg("storeTimeLimit"),
			"Set the event buffer storage duration limit.")
		.def("getNumberOfEvents", &dv::features::EventFeatureLKTracker<>::getNumberOfEvents,
			"Get the number of latest events that are going to be accumulated for each frame. The default number of "
			"event is a third of of total pixels in the sensor.")
		.def("setNumberOfEvents", &dv::features::EventFeatureLKTracker<>::setNumberOfEvents, py::arg("numberOfEvents"),
			"Set the number of latest events that are going to be accumulated for each frame. The default number of "
			"event is a third of of total pixels in the sensor.")
		.def("runTracking", &dv::features::EventFeatureLKTracker<>::runTracking,
			"Perform the tracking and cache the results.")
		.def("removeTracks", &dv::features::EventFeatureLKTracker<>::removeTracks, py::arg("trackIds"),
			"Remove tracks from cached results, so the wouldn't be tracked anymore. TrackIds are the `class_id` value "
			"of the keypoint structure.")
		.def("isLookbackRejectionEnabled", &dv::features::EventFeatureLKTracker<>::isLookbackRejectionEnabled,
			"Check whether lookback is enabled.")
		.def("setLookbackRejection", &dv::features::EventFeatureLKTracker<>::setLookbackRejection,
			py::arg("lookbackRejection"),
			"Enable or disable lookback rejection based on Forward-Backward error. Lookback rejection applies "
			"Lucas-Kanade tracking backwards after running the usual tracking and rejects any tracks that fails to "
			"successfully track back to same approximate location by measuring Euclidean distance. Euclidean distance "
			"threshold for rejection can be set using `setRejectionDistanceThreshold` method. This is a real-time "
			"implementation of the method proposed by Zdenek et al. 2010, that only performs forward-backward error "
			"measurement within a single pair of latest and previous frame: "
			"http://kahlan.eps.surrey.ac.uk/featurespace/tld/Publications/2010_icpr.pdf")
		.def("getRejectionDistanceThreshold", &dv::features::EventFeatureLKTracker<>::getRejectionDistanceThreshold,
			"Get the current rejection distance threshold for the lookback rejection feature.")
		.def("setRejectionDistanceThreshold", &dv::features::EventFeatureLKTracker<>::setRejectionDistanceThreshold,
			py::arg("rejectionDistanceThreshold"),
			"Set the threshold for lookback rejection feature. This value is a maximum Euclidean distance value that "
			"is considered successful when performing backwards tracking check after forward tracking. If the backward "
			"tracked feature location is further away from initial position than this given value, the tracker will "
			"reject the track as a failed track. See method `setLookbackRejection` documentation for further "
			"explanation of the approach.")
		.def("getConstantDepth", &dv::features::EventFeatureLKTracker<>::getConstantDepth,
			"Get currently assumed constant depth value. It is used if no depth measurements are provided.")
		.def("setConstantDepth", &dv::features::EventFeatureLKTracker<>::setConstantDepth, "depth"_a,
			"Set constant depth value that is assumed if no depth measurement is passed using "
			"`accept(dv::measurements::Depth)`. By default the constant depth is assumed to be 3.0 meters, which is "
			"just a reasonable guess.");
}
