/* +------------------------------------------------------------------------+
   |                     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/config.h>  // HAVE_SINCOS
#include <mrpt/math/TPose2D.h>
#include <mrpt/math/wrap2pi.h>
#include <mrpt/poses/CPoint2D.h>
#include <mrpt/poses/CPoint3D.h>
#include <mrpt/poses/CPose2D.h>
#include <mrpt/poses/CPose3D.h>
#include <mrpt/serialization/CArchive.h>
#include <mrpt/serialization/CSchemeArchiveBase.h>

#include <Eigen/Dense>
#include <limits>

using namespace mrpt;
using namespace mrpt::math;
using namespace mrpt::poses;

IMPLEMENTS_SERIALIZABLE(CPose2D, CSerializable, mrpt::poses)

/*---------------------------------------------------------------
	Constructors
  ---------------------------------------------------------------*/
CPose2D::CPose2D() { m_coords[0] = m_coords[1] = 0; }
CPose2D::CPose2D(const double x, const double y, const double _phi)
	: m_phi(_phi), m_cossin_uptodate(false)
{
	m_coords[0] = x;
	m_coords[1] = y;
	normalizePhi();
}

CPose2D::CPose2D(const CPoint2D& p) : m_phi(0), m_cossin_uptodate(false)
{
	m_coords[0] = p.x();
	m_coords[1] = p.y();
}

CPose2D::CPose2D(const CPose3D& p) : m_phi(p.yaw()), m_cossin_uptodate(false)
{
	m_coords[0] = p.x();
	m_coords[1] = p.y();
}

uint8_t CPose2D::serializeGetVersion() const { return 1; }
void CPose2D::serializeTo(mrpt::serialization::CArchive& out) const
{
	// The coordinates:
	out << m_coords[0] << m_coords[1] << m_phi;
}
void CPose2D::serializeFrom(mrpt::serialization::CArchive& in, uint8_t version)
{
	switch (version)
	{
		case 0:
		{
			// The coordinates:
			float x0, y0, phi0;
			in >> x0 >> y0 >> phi0;
			m_coords[0] = x0;
			m_coords[1] = y0;
			this->phi(phi0);
		}
		break;
		case 1:
		{
			// The coordinates:
			in >> m_coords[0] >> m_coords[1] >> m_phi;
			m_cossin_uptodate = false;
		}
		break;
		default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version);
	};
}

void CPose2D::serializeTo(mrpt::serialization::CSchemeArchiveBase& out) const
{
	SCHEMA_SERIALIZE_DATATYPE_VERSION(1);
	out["x"] = m_coords[0];
	out["y"] = m_coords[1];
	out["phi"] = m_phi;
}
void CPose2D::serializeFrom(mrpt::serialization::CSchemeArchiveBase& in)
{
	uint8_t version;
	SCHEMA_DESERIALIZE_DATATYPE_VERSION();
	switch (version)
	{
		case 1:
		{
			m_coords[0] = static_cast<double>(in["x"]);
			m_coords[1] = static_cast<double>(in["y"]);
			m_phi = static_cast<double>(in["phi"]);
		}
		break;
		default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version);
	}
}

/**  Textual output stream function.
 */
std::ostream& mrpt::poses::operator<<(std::ostream& o, const CPose2D& p)
{
	o << format("(%.03f,%.03f,%.02fdeg)", p.x(), p.y(), RAD2DEG(p.phi()));
	return o;
}

/*---------------------------------------------------------------
The operator a="this"+D is the pose compounding operator.
 ---------------------------------------------------------------*/
CPose2D CPose2D::operator+(const CPose2D& D) const
{
	update_cached_cos_sin();

	return CPose2D(
		m_coords[0] + D.m_coords[0] * m_cosphi - D.m_coords[1] * m_sinphi,
		m_coords[1] + D.m_coords[0] * m_sinphi + D.m_coords[1] * m_cosphi,
		m_phi + D.m_phi);
}

/*---------------------------------------------------------------
				composeFrom
 ---------------------------------------------------------------*/
void CPose2D::composeFrom(const CPose2D& A, const CPose2D& B)
{
	A.update_cached_cos_sin();

	// Use temporary variables for the cases (A==this) or (B==this)
	const double new_x =
		A.m_coords[0] + B.m_coords[0] * A.m_cosphi - B.m_coords[1] * A.m_sinphi;
	const double new_y =
		A.m_coords[1] + B.m_coords[0] * A.m_sinphi + B.m_coords[1] * A.m_cosphi;
	m_coords[0] = new_x;
	m_coords[1] = new_y;

	m_phi = math::wrapToPi(A.m_phi + B.m_phi);
	m_cossin_uptodate = false;
}

/*---------------------------------------------------------------
				getRotationMatrix
 ---------------------------------------------------------------*/
void CPose2D::getRotationMatrix(mrpt::math::CMatrixDouble22& R) const
{
	update_cached_cos_sin();
	R(0, 0) = m_cosphi;
	R(0, 1) = -m_sinphi;
	R(1, 0) = m_sinphi;
	R(1, 1) = m_cosphi;
}

void CPose2D::getRotationMatrix(mrpt::math::CMatrixDouble33& R) const
{
	update_cached_cos_sin();
	R(0, 0) = m_cosphi;
	R(0, 1) = -m_sinphi;
	R(0, 2) = 0;
	R(1, 0) = m_sinphi;
	R(1, 1) = m_cosphi;
	R(1, 2) = 0;
	R(2, 0) = 0;
	R(2, 1) = 0;
	R(2, 2) = 1;
}

/*---------------------------------------------------------------
The operator a="this"+D is the pose compounding operator.
 ---------------------------------------------------------------*/
CPose3D CPose2D::operator+(const CPose3D& D) const
{
	return CPose3D(*this) + D;
}

/*---------------------------------------------------------------
The operator u'="this"+u is the pose/point compounding operator.
 ---------------------------------------------------------------*/
CPoint2D CPose2D::operator+(const CPoint2D& u) const
{
	update_cached_cos_sin();

	return CPoint2D(
		m_coords[0] + u.x() * m_cosphi - u.y() * m_sinphi,
		m_coords[1] + u.x() * m_sinphi + u.y() * m_cosphi);
}

/** An alternative, slightly more efficient way of doing \f$ G = P \oplus L \f$
 * with G and L being 2D points and P this 2D pose.  */
void CPose2D::composePoint(double lx, double ly, double& gx, double& gy) const
{
	update_cached_cos_sin();

	gx = m_coords[0] + lx * m_cosphi - ly * m_sinphi;
	gy = m_coords[1] + lx * m_sinphi + ly * m_cosphi;
}

void CPose2D::composePoint(
	const mrpt::math::TPoint2D& l, mrpt::math::TPoint2D& g) const
{
	this->composePoint(l.x, l.y, g.x, g.y);
}

/** overload \f$ G = P \oplus L \f$ with G and L being 3D points and P this 2D
 * pose (the "z" coordinate remains unmodified) */
void CPose2D::composePoint(
	const mrpt::math::TPoint3D& l, mrpt::math::TPoint3D& g) const
{
	this->composePoint(l.x, l.y, l.z, g.x, g.y, g.z);
}
mrpt::math::TPoint3D CPose2D::composePoint(const mrpt::math::TPoint3D& l) const
{
	mrpt::math::TPoint3D g;
	composePoint(l, g);
	return g;
}

void CPose2D::composePoint(
	double lx, double ly, double lz, double& gx, double& gy, double& gz) const
{
	update_cached_cos_sin();
	gx = m_coords[0] + lx * m_cosphi - ly * m_sinphi;
	gy = m_coords[1] + lx * m_sinphi + ly * m_cosphi;
	gz = lz;
}

void CPose2D::inverseComposePoint(
	const double gx, const double gy, double& lx, double& ly) const
{
	update_cached_cos_sin();

	const double Ax = gx - m_coords[0];
	const double Ay = gy - m_coords[1];

	lx = Ax * m_cosphi + Ay * m_sinphi;
	ly = -Ax * m_sinphi + Ay * m_cosphi;
}
void CPose2D::inverseComposePoint(
	const mrpt::math::TPoint2D& g, mrpt::math::TPoint2D& l) const
{
	inverseComposePoint(g.x, g.y, l.x, l.y);
}
mrpt::math::TPoint2D CPose2D::inverseComposePoint(
	const mrpt::math::TPoint2D& g) const
{
	mrpt::math::TPoint2D l;
	inverseComposePoint(g, l);
	return l;
}

/*---------------------------------------------------------------
The operator u'="this"+u is the pose/point compounding operator.
 ---------------------------------------------------------------*/
CPoint3D CPose2D::operator+(const CPoint3D& u) const
{
	update_cached_cos_sin();

	return CPoint3D(
		m_coords[0] + u.x() * m_cosphi - u.y() * m_sinphi,
		m_coords[1] + u.x() * m_sinphi + u.y() * m_cosphi, u.z());
}

/*---------------------------------------------------------------
The operator D="this"-b is the pose inverse compounding operator.
   The resulting pose "D" is the diference between this pose and "b"
 ---------------------------------------------------------------*/
void CPose2D::inverseComposeFrom(const CPose2D& A, const CPose2D& B)
{
	B.update_cached_cos_sin();

	m_coords[0] = (A.m_coords[0] - B.m_coords[0]) * B.m_cosphi +
		(A.m_coords[1] - B.m_coords[1]) * B.m_sinphi;
	m_coords[1] = -(A.m_coords[0] - B.m_coords[0]) * B.m_sinphi +
		(A.m_coords[1] - B.m_coords[1]) * B.m_cosphi;
	m_phi = math::wrapToPi(A.m_phi - B.m_phi);
	m_cossin_uptodate = false;
}

/*---------------------------------------------------------------
 Scalar sum of components: This is diferent from poses
   composition, which is implemented as "+" operators in "CPose" derived
 classes.
 ---------------------------------------------------------------*/
void CPose2D::AddComponents(const CPose2D& p)
{
	m_coords[0] += p.m_coords[0];
	m_coords[1] += p.m_coords[1];
	m_phi += p.m_phi;
	m_cossin_uptodate = false;
}

/*---------------------------------------------------------------
 Scalar multiplication.
 ---------------------------------------------------------------*/
void CPose2D::operator*=(const double s)
{
	m_coords[0] *= s;
	m_coords[1] *= s;
	m_phi *= s;
	m_cossin_uptodate = false;
}

/*---------------------------------------------------------------
	Returns the corresponding 4x4 homogeneous
	  transformation matrix for the point(translation),
	  or pose (translation+orientation).
---------------------------------------------------------------*/
void CPose2D::getHomogeneousMatrix(CMatrixDouble44& m) const
{
	m.setIdentity();
	m(0, 3) = m_coords[0];
	m(1, 3) = m_coords[1];

	update_cached_cos_sin();

	m(0, 0) = m_cosphi;
	m(0, 1) = -m_sinphi;
	m(1, 0) = m_sinphi;
	m(1, 1) = m_cosphi;
}

/** Forces "phi" to be in the range [-pi,pi];
 */
void CPose2D::normalizePhi()
{
	m_phi = math::wrapToPi(m_phi);
	m_cossin_uptodate = false;
}

CPose2D::CPose2D(const mrpt::math::TPose2D& o)
	: m_phi(o.phi), m_cossin_uptodate(false)
{
	m_coords[0] = o.x;
	m_coords[1] = o.y;
}

CPose2D::CPose2D(const CPoint3D& o) : m_phi(0), m_cossin_uptodate(false)
{
	this->m_coords[0] = o.x();
	this->m_coords[1] = o.y();
	normalizePhi();
}

/*---------------------------------------------------------------
		unary -
---------------------------------------------------------------*/
CPose2D mrpt::poses::operator-(const CPose2D& b)
{
	CPose2D ret = b;
	ret.inverse();
	return ret;
}

/** Convert this pose into its inverse, saving the result in itself. \sa
 * operator- */
void CPose2D::inverse()
{
	update_cached_cos_sin();
	const double x = m_coords[0];
	const double y = m_coords[1];

	m_coords[0] = -x * m_cosphi - y * m_sinphi;
	m_coords[1] = x * m_sinphi - y * m_cosphi;
	m_phi = -m_phi;
	m_cossin_uptodate = false;
}

void CPose2D::asVector(vector_t& v) const
{
	v[0] = m_coords[0];
	v[1] = m_coords[1];
	v[2] = m_phi;
}

bool mrpt::poses::operator==(const CPose2D& p1, const CPose2D& p2)
{
	return (p1.x() == p2.x()) && (p1.y() == p2.y()) && (p1.phi() == p2.phi());
}

bool mrpt::poses::operator!=(const CPose2D& p1, const CPose2D& p2)
{
	return (p1.x() != p2.x()) || (p1.y() != p2.y()) || (p1.phi() != p2.phi());
}

TPoint2D mrpt::poses::operator+(const CPose2D& pose, const TPoint2D& u)
{
	const double ccos = pose.phi_cos();
	const double ssin = pose.phi_sin();
	return TPoint2D(
		pose.x() + u.x * ccos - u.y * ssin, pose.y() + u.x * ssin + u.y * ccos);
}

/** Make \f$ this = this \oplus b \f$  */
CPose2D& CPose2D::operator+=(const CPose2D& b)
{
	composeFrom(*this, b);
	return *this;
}

void CPose2D::fromString(const std::string& s)
{
	CMatrixDouble m;
	if (!m.fromMatlabStringFormat(s))
		THROW_EXCEPTION_FMT(
			"Malformed expression in ::fromString, s=\"%s\"", s.c_str());
	ASSERTMSG_(m.rows() == 1 && m.cols() == 3, "Expected vector length=3");
	x(m(0, 0));
	y(m(0, 1));
	phi(DEG2RAD(m(0, 2)));
}

void CPose2D::fromStringRaw(const std::string& s)
{
	this->fromString("[" + s + "]");
}

double CPose2D::distance2DFrobeniusTo(const CPose2D& p) const
{
	return std::sqrt(
		square(p.x() - x()) + square(p.y() - y()) +
		4 * (1 - cos(p.phi() - phi())));
}

CPose3D CPose2D::operator-(const CPose3D& b) const
{
	CMatrixDouble44 RES = CMatrixDouble44(
		b.getInverseHomogeneousMatrixVal<CMatrixDouble44>() *
		getHomogeneousMatrixVal<CMatrixDouble44>());
	return CPose3D(RES);
}

CPose2D CPose2D::getOppositeScalar() const
{
	return CPose2D(-m_coords[0], -m_coords[1], -m_phi);
}

std::string CPose2D::asString() const
{
	return mrpt::format("[%f %f %fdeg]", x(), y(), mrpt::RAD2DEG(m_phi));
}

void CPose2D::setToNaN()
{
	for (int i = 0; i < 3; i++)
		(*this)[i] = std::numeric_limits<double>::quiet_NaN();
}

void CPose2D::update_cached_cos_sin() const
{
	if (m_cossin_uptodate) return;
#ifdef HAVE_SINCOS
	::sincos(m_phi, &m_sinphi, &m_cosphi);
#else
	m_cosphi = ::cos(m_phi);
	m_sinphi = ::sin(m_phi);
#endif
	m_cossin_uptodate = true;
}

mrpt::math::TPose2D CPose2D::asTPose() const
{
	return mrpt::math::TPose2D(x(), y(), phi());
}
