/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "poses-precomp.h"	// Precompiled headers
//
#include <mrpt/poses/CPose3DInterpolator.h>
#include <mrpt/serialization/stl_serialization.h>

#include <Eigen/Dense>

#include "CPoseInterpolatorBase.hpp"  // templ impl

using namespace mrpt::poses;

IMPLEMENTS_SERIALIZABLE(CPose3DInterpolator, CSerializable, mrpt::poses)

uint8_t CPose3DInterpolator::serializeGetVersion() const { return 1; }
void CPose3DInterpolator::serializeTo(mrpt::serialization::CArchive& out) const
{
	out << m_path;	// v1: change container element CPose3D->TPose3D
}
void CPose3DInterpolator::serializeFrom(
	mrpt::serialization::CArchive& in, uint8_t version)
{
	switch (version)
	{
		case 0:
		{
			std::map<mrpt::Clock::time_point, mrpt::poses::CPose3D> old_path;
			in >> old_path;
			m_path.clear();
			for (const auto& p : old_path)
			{
				m_path[p.first] = p.second.asTPose();
			}
		}
		break;
		case 1:
		{
			in >> m_path;
		}
		break;
		default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version);
	};
}

namespace mrpt::poses
{
// Specialization for DIM=3
template <>
void CPoseInterpolatorBase<3>::impl_interpolation(
	const TTimePosePair& p1, const TTimePosePair& p2, const TTimePosePair& p3,
	const TTimePosePair& p4, const TInterpolatorMethod method,
	const mrpt::Clock::time_point& t, pose_t& out_interp) const
{
	using mrpt::math::TPose3D;
	mrpt::math::CVectorFixedDouble<4> X, Y, Z, yaw, pitch, roll;
	mrpt::math::CVectorFixedDouble<4> ts;
	using doubleDuration = std::chrono::duration<double>;
	doubleDuration durationT = t.time_since_epoch();
	double td = durationT.count();
	ts[0] =
		std::chrono::duration_cast<doubleDuration>(p1.first.time_since_epoch())
			.count();
	ts[1] =
		std::chrono::duration_cast<doubleDuration>(p2.first.time_since_epoch())
			.count();
	ts[2] =
		std::chrono::duration_cast<doubleDuration>(p3.first.time_since_epoch())
			.count();
	ts[3] =
		std::chrono::duration_cast<doubleDuration>(p4.first.time_since_epoch())
			.count();

	X[0] = p1.second.x;
	Y[0] = p1.second.y;
	Z[0] = p1.second.z;
	X[1] = p2.second.x;
	Y[1] = p2.second.y;
	Z[1] = p2.second.z;
	X[2] = p3.second.x;
	Y[2] = p3.second.y;
	Z[2] = p3.second.z;
	X[3] = p4.second.x;
	Y[3] = p4.second.y;
	Z[3] = p4.second.z;

	yaw[0] = p1.second.yaw;
	pitch[0] = p1.second.pitch;
	roll[0] = p1.second.roll;
	yaw[1] = p2.second.yaw;
	pitch[1] = p2.second.pitch;
	roll[1] = p2.second.roll;
	yaw[2] = p3.second.yaw;
	pitch[2] = p3.second.pitch;
	roll[2] = p3.second.roll;
	yaw[3] = p4.second.yaw;
	pitch[3] = p4.second.pitch;
	roll[3] = p4.second.roll;

	unwrap2PiSequence(yaw);
	unwrap2PiSequence(pitch);
	unwrap2PiSequence(roll);

	// Target interpolated values:
	switch (method)
	{
		case imSpline:
		{
			// ---------------------------------------
			//    SPLINE INTERPOLATION
			// ---------------------------------------
			out_interp.x = math::spline(td, ts, X);
			out_interp.y = math::spline(td, ts, Y);
			out_interp.z = math::spline(td, ts, Z);
			out_interp.yaw = math::spline(td, ts, yaw, true);  // Wrap 2pi
			out_interp.pitch = math::spline(td, ts, pitch, true);
			out_interp.roll = math::spline(td, ts, roll, true);
		}
		break;

		case imLinear2Neig:
		{
			out_interp.x =
				math::interpolate2points(td, ts[1], X[1], ts[2], X[2]);
			out_interp.y =
				math::interpolate2points(td, ts[1], Y[1], ts[2], Y[2]);
			out_interp.z =
				math::interpolate2points(td, ts[1], Z[1], ts[2], Z[2]);
			out_interp.yaw = math::interpolate2points(
				td, ts[1], yaw[1], ts[2], yaw[2], true);  // Wrap 2pi
			out_interp.pitch = math::interpolate2points(
				td, ts[1], pitch[1], ts[2], pitch[2], true);
			out_interp.roll = math::interpolate2points(
				td, ts[1], roll[1], ts[2], roll[2], true);
		}
		break;

		case imLinear4Neig:
		{
			out_interp.x =
				math::leastSquareLinearFit<double, decltype(ts), 4>(td, ts, X);
			out_interp.y =
				math::leastSquareLinearFit<double, decltype(ts), 4>(td, ts, Y);
			out_interp.z =
				math::leastSquareLinearFit<double, decltype(ts), 4>(td, ts, Z);
			out_interp.yaw =
				math::leastSquareLinearFit<double, decltype(ts), 4>(
					td, ts, yaw, true);	 // Wrap 2pi
			out_interp.pitch =
				math::leastSquareLinearFit<double, decltype(ts), 4>(
					td, ts, pitch, true);
			out_interp.roll =
				math::leastSquareLinearFit<double, decltype(ts), 4>(
					td, ts, roll, true);
		}
		break;

		case imSSLLLL:
		{
			out_interp.x = math::spline(td, ts, X);
			out_interp.y = math::spline(td, ts, Y);
			out_interp.z =
				math::leastSquareLinearFit<double, decltype(ts), 4>(td, ts, Z);
			out_interp.yaw =
				math::leastSquareLinearFit<double, decltype(ts), 4>(
					td, ts, yaw, true);	 // Wrap 2pi
			out_interp.pitch =
				math::leastSquareLinearFit<double, decltype(ts), 4>(
					td, ts, pitch, true);
			out_interp.roll =
				math::leastSquareLinearFit<double, decltype(ts), 4>(
					td, ts, roll, true);
		}
		break;

		case imSSLSLL:
		{
			out_interp.x = math::spline(td, ts, X);
			out_interp.y = math::spline(td, ts, Y);
			out_interp.z =
				math::leastSquareLinearFit<double, decltype(ts), 4>(td, ts, Z);
			out_interp.yaw = math::spline(td, ts, yaw, true);  // Wrap 2pi
			out_interp.pitch =
				math::leastSquareLinearFit<double, decltype(ts), 4>(
					td, ts, pitch, true);
			out_interp.roll =
				math::leastSquareLinearFit<double, decltype(ts), 4>(
					td, ts, roll, true);
		}
		break;

		case imLinearSlerp:
		{
			const double ratio = (td - ts[1]) / (ts[2] - ts[1]);
			mrpt::math::slerp_ypr(
				TPose3D(0, 0, 0, yaw[1], pitch[1], roll[1]),
				TPose3D(0, 0, 0, yaw[2], pitch[2], roll[2]), ratio, out_interp);

			out_interp.x =
				math::interpolate2points(td, ts[1], X[1], ts[2], X[2]);
			out_interp.y =
				math::interpolate2points(td, ts[1], Y[1], ts[2], Y[2]);
			out_interp.z =
				math::interpolate2points(td, ts[1], Z[1], ts[2], Z[2]);
		}
		break;

		case imSplineSlerp:
		{
			const double ratio = (td - ts[1]) / (ts[2] - ts[1]);
			mrpt::math::slerp_ypr(
				TPose3D(0, 0, 0, yaw[1], pitch[1], roll[1]),
				TPose3D(0, 0, 0, yaw[2], pitch[2], roll[2]), ratio, out_interp);

			out_interp.x = math::spline(td, ts, X);
			out_interp.y = math::spline(td, ts, Y);
			out_interp.z = math::spline(td, ts, Z);
		}
		break;

		default: THROW_EXCEPTION("Unknown value for interpolation method!");
	};	// end switch
}

// Explicit instantations:
template class CPoseInterpolatorBase<3>;
}  // namespace mrpt::poses
