// Copyright 2019-2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
 * @file
 * @brief  OpenCV calibration helpers.
 * @author Pete Black <pblack@collabora.com>
 * @author Jakob Bornecrantz <jakob@collabora.com>
 * @author Ryan Pavlik <ryan.pavlik@collabora.com>
 * @ingroup aux_tracking
 */

#pragma once

#ifndef __cplusplus
#error "This header is C++-only."
#endif

#include "tracking/t_tracking.h"

#include <opencv2/opencv.hpp>
#include <sys/stat.h>


/*!
 * Save raw calibration data to file, hack until prober has storage for such
 * things.
 */
extern "C" bool
t_file_save_raw_data_hack(struct t_stereo_camera_calibration *data);

/*!
 * @brief Essential calibration data wrapped for C++.
 *
 * Just like the cv::Mat that it holds, this object does not own all the memory
 * it points to!
 */
struct CameraCalibrationWrapper
{
	xrt_size &image_size_pixels;
	cv::Mat intrinsics_mat;
	cv::Mat distortion_mat;
	cv::Mat distortion_fisheye_mat;
	bool &use_fisheye;

	CameraCalibrationWrapper(t_camera_calibration &calib)
	    : image_size_pixels(calib.image_size_pixels),
	      intrinsics_mat(3, 3, CV_64F, &calib.intrinsics[0][0]),
	      distortion_mat(
	          XRT_DISTORTION_MAX_DIM, 1, CV_64F, &calib.distortion[0]),
	      distortion_fisheye_mat(
	          4, 1, CV_64F, &calib.distortion_fisheye[0]),
	      use_fisheye(calib.use_fisheye)
	{
		assert(isDataStorageValid());
	}

	//! Try to verify nothing was reallocated.
	bool
	isDataStorageValid() const noexcept
	{
		return intrinsics_mat.size() == cv::Size(3, 3) &&
		       distortion_mat.size() ==
		           cv::Size(1, XRT_DISTORTION_MAX_DIM) &&
		       distortion_fisheye_mat.size() == cv::Size(1, 4);
	}
};


/*!
 * @brief Essential stereo calibration data wrapped for C++.
 *
 * Just like the cv::Mat that it holds, this object does not own (all) the
 * memory it points to!
 */
struct StereoCameraCalibrationWrapper
{
	CameraCalibrationWrapper l_calibration;
	CameraCalibrationWrapper r_calibration;
	cv::Mat camera_translation_mat;
	cv::Mat camera_rotation_mat;
	cv::Mat camera_essential_mat;
	cv::Mat camera_fundamental_mat;

	StereoCameraCalibrationWrapper(t_stereo_camera_calibration &stereo)
	    : l_calibration(stereo.l_calibration),
	      r_calibration(stereo.r_calibration),
	      camera_translation_mat(
	          3, 1, CV_64F, &stereo.camera_translation[0]),
	      camera_rotation_mat(3, 3, CV_64F, &stereo.camera_rotation[0][0]),
	      camera_essential_mat(
	          3, 3, CV_64F, &stereo.camera_essential[0][0]),
	      camera_fundamental_mat(
	          3, 3, CV_64F, &stereo.camera_fundamental[0][0])
	{
		assert(isDataStorageValid());
	}

	bool
	isDataStorageValid() const noexcept
	{
		return camera_translation_mat.size() == cv::Size(1, 3) &&
		       camera_rotation_mat.size() == cv::Size(3, 3) &&

		       camera_essential_mat.size() == cv::Size(3, 3) &&
		       camera_fundamental_mat.size() == cv::Size(3, 3) &&
		       l_calibration.isDataStorageValid() &&
		       r_calibration.isDataStorageValid();
	}
};


/*!
 * @brief An x,y pair of matrices for the remap() function.
 *
 * @see calibration_get_undistort_map
 */
struct RemapPair
{
	cv::Mat remap_x;
	cv::Mat remap_y;
};

/*!
 * @brief Prepare undistortion/normalization remap structures for a rectilinear
 * or fisheye image.
 *
 * @param calib A single camera calibration structure.
 * @param rectify_transform_optional A rectification transform to apply, if
 * desired.
 * @param new_camera_matrix_optional Unlike OpenCV, the default/empty matrix
 * here uses the input camera matrix as your output camera matrix.
 */
RemapPair
calibration_get_undistort_map(
    t_camera_calibration &calib,
    cv::InputArray rectify_transform_optional = cv::noArray(),
    cv::Mat new_camera_matrix_optional = cv::Mat());

/*!
 * @brief Rectification maps as well as transforms for a stereo camera.
 *
 * Computed in the constructor from saved calibration data.
 */
struct StereoRectificationMaps
{
	RemapPair l_rectify;
	cv::Mat l_rotation_mat = {};
	cv::Mat l_projection_mat = {};

	RemapPair r_rectify;
	cv::Mat r_rotation_mat = {};
	cv::Mat r_projection_mat = {};

	//! Disparity and position to camera world coordinates.
	cv::Mat disparity_to_depth_mat = {};

	StereoRectificationMaps(t_stereo_camera_calibration &data);
};