#pragma once

#include <opencv2/core/types.hpp>
#include <pybind11/pybind11.h>

#include <utility>

namespace py = pybind11;

// Cast for cv::Point2f between tuple
template<>
struct py::detail::type_caster<cv::Point2f> {
	PYBIND11_TYPE_CASTER(cv::Point2f, _("tuple_xy"));

	bool load(handle obj, bool) {
		if (!py::isinstance<py::tuple>(obj)) {
			return false;
		}

		auto pt = reinterpret_borrow<py::tuple>(obj);
		if (pt.size() != 2) {
			return false;
		}

		value = cv::Point2f(pt[0].cast<float>(), pt[1].cast<float>());
		return true;
	}

	static handle cast(const cv::Point2f &point, return_value_policy, handle) {
		return py::make_tuple(point.x, point.y).release();
	}
};

// Cast cv::Point3f between tuple
template<>
struct py::detail::type_caster<cv::Point3f> {
	PYBIND11_TYPE_CASTER(cv::Point3f, _("tuple_xyz"));

	bool load(handle obj, bool) {
		if (!py::isinstance<py::tuple>(obj)) {
			return false;
		}

		auto pt = reinterpret_borrow<py::tuple>(obj);
		if (pt.size() != 3) {
			return false;
		}

		value = cv::Point3f(pt[0].cast<float>(), pt[1].cast<float>(), pt[2].cast<float>());
		return true;
	}

	static handle cast(const cv::Point3f &point, return_value_policy, handle) {
		return py::make_tuple(point.x, point.y, point.z).release();
	}
};

// Cast cv::Point2i between tuple
template<>
struct py::detail::type_caster<cv::Point2i> {
	PYBIND11_TYPE_CASTER(cv::Point2i, _("tuple_xy"));

	bool load(handle obj, bool) {
		if (!py::isinstance<py::tuple>(obj)) {
			return false;
		}

		auto pt = reinterpret_borrow<py::tuple>(obj);
		if (pt.size() != 2) {
			return false;
		}

		value = cv::Point2i(pt[0].cast<int>(), pt[1].cast<int>());
		return true;
	}

	static handle cast(const cv::Point2i &point, return_value_policy, handle) {
		return py::make_tuple(point.x, point.y).release();
	}
};

// Cast cv::Size between tuple
template<>
struct py::detail::type_caster<cv::Size> {
	PYBIND11_TYPE_CASTER(cv::Size, _("tuple_xy"));

	bool load(handle obj, bool) {
		if (!py::isinstance<py::tuple>(obj)) {
			return false;
		}

		auto pt = reinterpret_borrow<py::tuple>(obj);
		if (pt.size() != 2) {
			return false;
		}

		value = cv::Size(pt[0].cast<int>(), pt[1].cast<int>());
		return true;
	}

	static handle cast(const cv::Size &resolution, return_value_policy, handle) {
		return py::make_tuple(resolution.width, resolution.height).release();
	}
};

// Cast cv::Rect between tuple
template<>
struct py::detail::type_caster<cv::Rect> {
	PYBIND11_TYPE_CASTER(cv::Rect, _("tuple_xywh"));

	bool load(handle obj, bool) {
		if (!py::isinstance<py::tuple>(obj)) {
			return false;
		}

		auto rect = reinterpret_borrow<py::tuple>(obj);
		if (rect.size() != 4) {
			return false;
		}

		value = cv::Rect(rect[0].cast<int>(), rect[1].cast<int>(), rect[2].cast<int>(), rect[3].cast<int>());
		return true;
	}

	static handle cast(const cv::Rect &rect, return_value_policy, handle) {
		return py::make_tuple(rect.x, rect.y, rect.width, rect.height).release();
	}
};

// Cast cv::Scalar between tuple
template<>
struct py::detail::type_caster<cv::Scalar> {
	PYBIND11_TYPE_CASTER(cv::Scalar, _("tuple_bgr"));

	bool load(handle obj, bool) {
		if (!py::isinstance<py::tuple>(obj)) {
			return false;
		}

		auto pt = reinterpret_borrow<py::tuple>(obj);
		if (pt.size() != 3) {
			return false;
		}

		value = cv::Scalar(pt[0].cast<double>(), pt[1].cast<double>(), pt[2].cast<double>());
		return true;
	}

	static handle cast(const cv::Scalar &scalar, return_value_policy, handle) {
		return py::make_tuple(scalar[0], scalar[1], scalar[2]).release();
	}
};
