#pragma once

#include "../../../include/dv-processing/data/geometry_types_base.hpp"

#include <pybind11/pybind11.h>

#include <utility>

namespace py = pybind11;

/**
 * @brief Cast the Point2f between tuple
 * @param m pybind The module to bind to
 */
template<>
struct py::detail::type_caster<dv::Point2f> {
	PYBIND11_TYPE_CASTER(dv::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 = dv::Point2f(pt[0].cast<float>(), pt[1].cast<float>());
		return true;
	}

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

/**
 * @brief Cast the Point3f between tuple
 * @param m pybind The module to bind to
 */
template<>
struct py::detail::type_caster<dv::Point3f> {
	PYBIND11_TYPE_CASTER(dv::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 = dv::Point3f(pt[0].cast<float>(), pt[1].cast<float>(), pt[2].cast<float>());
		return true;
	}

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

/**
 * @brief Cast the Rect between tuple
 * @param m pybind The module to bind to
 */
template<>
struct py::detail::type_caster<dv::Vec2f> {
	PYBIND11_TYPE_CASTER(dv::Vec2f, _("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 = dv::Vec2f(pt[0].cast<float>(), pt[1].cast<float>());
		return true;
	}

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

/**
 * @brief Cast the Vec3f between tuple
 * @param m pybind The module to bind to
 */
template<>
struct py::detail::type_caster<dv::Vec3f> {
	PYBIND11_TYPE_CASTER(dv::Vec3f, _("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 = dv::Vec3f(pt[0].cast<float>(), pt[1].cast<float>(), pt[2].cast<float>());
		return true;
	}

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

/**
 * @brief Cast the Quaternion between tuple
 * @param m pybind The module to bind to
 */
template<>
struct py::detail::type_caster<dv::Quaternion> {
	PYBIND11_TYPE_CASTER(dv::Quaternion, _("tuple_wxyz"));

	bool load(handle obj, bool) {
		if (!py::isinstance<py::tuple>(obj)) {
			return false;
		}
		py::tuple quat = reinterpret_borrow<py::tuple>(obj);
		if (quat.size() != 4) {
			return false;
		}

		value = dv::Quaternion(
			quat[0].cast<float>(), quat[1].cast<float>(), quat[2].cast<float>(), quat[3].cast<float>());
		return true;
	}

	static handle cast(const dv::Quaternion &quat, return_value_policy, handle) {
		return py::make_tuple(quat.w(), quat.x(), quat.y(), quat.z()).release();
	}
};
