#pragma once

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

#include <pybind11/pybind11.h>

namespace py = pybind11;

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

	py::class_<dv::features::LucasKanadeConfig>(
		m, "LucasKanadeConfig", "Lucas-Kanade tracker configuration parameters.")
		.def(py::init<>())
		.def("__repr__",
			[](const dv::features::LucasKanadeConfig &config) -> std::string {
				return "LucasKanadeConfig()";
			})
		.def_readwrite("maskedFeatureDetect", &dv::features::LucasKanadeConfig::maskedFeatureDetect)
		.def_readwrite("terminationEpsilon", &dv::features::LucasKanadeConfig::terminationEpsilon)
		.def_readwrite("numPyrLayers", &dv::features::LucasKanadeConfig::numPyrLayers)
		.def_readwrite("searchWindowSize", &dv::features::LucasKanadeConfig::searchWindowSize);

	py::class_<dv::features::ImageFeatureLKTracker>(
		m, "ImageFeatureLKTracker", "A feature based sparse Lucas-Kanade feature tracker based on image pyramids.")
		.def_static(
			"RegularTracker",
			[](const cv::Size &resolution, const dv::features::LucasKanadeConfig &config) {
				return dv::features::ImageFeatureLKTracker::RegularTracker(resolution, config).release();
			},
			py::arg("resolution"), py::arg("config") = dv::features::LucasKanadeConfig(),
			"Create a tracker instance that performs tracking of features on images.")
		.def_static(
			"MotionAwareTracker",
			[](const dv::camera::CameraGeometry::SharedPtr &camera, const dv::features::LucasKanadeConfig &config) {
				return dv::features::ImageFeatureLKTracker::MotionAwareTracker(camera, config).release();
			},
			py::arg("camera"), py::arg("config") = dv::features::LucasKanadeConfig(),
			"Create a tracker instance that performs tracking of features on images. Features are  Additionally, "
			"camera motion and scene depth are used to predict probable locations of the feature tracks, increasing "
			"the quality of tracking.")
		.def(
			"accept",
			[](dv::features::ImageFeatureLKTracker &self, const dv::Frame &frame) {
				self.accept(frame);
			},
			py::arg("frame"), "Add an input image for the tracker. Image pyramid will be built from the given image.")
		.def(
			"accept",
			[](dv::features::ImageFeatureLKTracker &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::ImageFeatureLKTracker &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::ImageFeatureLKTracker &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::ImageFeatureLKTracker &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::ImageFeatureLKTracker &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::ImageFeatureLKTracker &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::ImageFeatureLKTracker &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::ImageFeatureLKTracker &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("getMaxTracks", &dv::features::ImageFeatureLKTracker::getMaxTracks, "Get the maximum number of tracks.")
		.def("setMaxTracks", &dv::features::ImageFeatureLKTracker::setMaxTracks, py::arg("maxTracks"),
			"Set the maximum number of tracks.")
		.def("runTracking", &dv::features::ImageFeatureLKTracker::runTracking,
			"Perform the tracking and cache the results.")
		.def("removeTracks", &dv::features::ImageFeatureLKTracker::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::ImageFeatureLKTracker::isLookbackRejectionEnabled,
			"Check whether lookback is enabled.")
		.def("setLookbackRejection", &dv::features::ImageFeatureLKTracker::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::ImageFeatureLKTracker::getRejectionDistanceThreshold,
			"Get the current rejection distance threshold for the lookback rejection feature.")
		.def("setRejectionDistanceThreshold", &dv::features::ImageFeatureLKTracker::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::ImageFeatureLKTracker::getConstantDepth,
			"Get currently assumed constant depth value. It is used if no depth measurements are provided.")
		.def("setConstantDepth", &dv::features::ImageFeatureLKTracker::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.");
}
