/* +------------------------------------------------------------------------+
   |                     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          |
   +------------------------------------------------------------------------+ */
#pragma once

#include <mrpt/core/Stringifyable.h>
#include <mrpt/math/CVectorFixed.h>
#include <mrpt/math/MatrixVectorBase.h>
#include <mrpt/math/TPoint2D.h>
#include <mrpt/poses/CPose.h>
#include <mrpt/serialization/CSerializable.h>

namespace mrpt::poses
{
class CPose3D;
/** A class used to store a 2D pose, including the 2D coordinate point and a
 * heading (phi) angle.
 *  Use this class instead of lightweight mrpt::math::TPose2D when pose/point
 * composition is to be called
 *  multiple times with the same pose, since this class caches calls to
 * expensive trigronometric functions.
 *
 * For a complete description of Points/Poses, see mrpt::poses::CPoseOrPoint,
 * or refer to [this documentation page]
 *(http://www.mrpt.org/tutorials/programming/maths-and-geometry/2d_3d_geometry/)
 *
 *  <div align=center>
 *   <img src="CPose2D.gif">
 *  </div>
 *
 * \note Read also: "A tutorial on SE(3) transformation parameterizations and
 * on-manifold optimization", Jose-Luis Blanco.
 * http://ingmec.ual.es/~jlblanco/papers/jlblanco2010geometry3D_techrep.pdf
 * \sa CPoseOrPoint,CPoint2D
 * \ingroup poses_grp
 */
class CPose2D : public CPose<CPose2D, 3>,
				public mrpt::serialization::CSerializable,
				public mrpt::Stringifyable
{
   public:
	DEFINE_SERIALIZABLE(CPose2D, mrpt::poses)
	DEFINE_SCHEMA_SERIALIZABLE()

   public:
	/** [x,y] */
	mrpt::math::CVectorFixedDouble<2> m_coords;

   protected:
	/** The orientation of the pose, in radians. */
	double m_phi{.0};
	/** Precomputed cos() & sin() of phi. */
	mutable double m_cosphi{1.0}, m_sinphi{.0};
	mutable bool m_cossin_uptodate{false};
	void update_cached_cos_sin() const;

   public:
	/** Default constructor (all coordinates to 0) */
	CPose2D();

	/** Constructor from an initial value of the pose.*/
	CPose2D(const double x, const double y, const double phi);

	/** Returns the identity transformation */
	static CPose2D Identity() { return CPose2D(); }

	/** Constructor from a CPoint2D object. */
	explicit CPose2D(const CPoint2D&);

	/** Aproximation!! Avoid its use, since information is lost. */
	explicit CPose2D(const CPose3D&);

	/** Constructor from lightweight object. */
	explicit CPose2D(const mrpt::math::TPose2D&);

	mrpt::math::TPose2D asTPose() const;

	/** Constructor from CPoint3D with information loss. */
	explicit CPose2D(const CPoint3D&);

	/** Fast constructor that leaves all the data uninitialized - call with
	 * UNINITIALIZED_POSE as argument */
	inline CPose2D(TConstructorFlags_Poses) : m_cossin_uptodate(false) {}
	/** Get the phi angle of the 2D pose (in radians) */
	inline double phi() const { return m_phi; }
	//! \overload
	inline double& phi()
	{
		m_cossin_uptodate = false;
		return m_phi;
	}  //-V659

	/** Get a (cached) value of cos(phi), recomputing it only once when phi
	 * changes. */
	inline double phi_cos() const
	{
		update_cached_cos_sin();
		return m_cosphi;
	}
	/** Get a (cached) value of sin(phi), recomputing it only once when phi
	 * changes. */
	inline double phi_sin() const
	{
		update_cached_cos_sin();
		return m_sinphi;
	}

	/** Set the phi angle of the 2D pose (in radians) */
	inline void phi(double angle)
	{
		m_phi = angle;
		m_cossin_uptodate = false;
	}

	/** Increment the PHI angle (without checking the 2 PI range, call
	 * normalizePhi is needed) */
	inline void phi_incr(const double Aphi)
	{
		m_phi += Aphi;
		m_cossin_uptodate = false;
	}

	/** Returns a 1x3 vector with [x y phi] */
	void asVector(vector_t& v) const;

	/** Returns the (x,y) translational part of the SE(2) transformation. */
	mrpt::math::TPoint2D translation() const
	{
		return {m_coords[0], m_coords[1]};
	}

	/** Returns the corresponding 4x4 homogeneous transformation matrix for the
	 * point(translation) or pose (translation+orientation).
	 * \sa getInverseHomogeneousMatrix
	 */
	void getHomogeneousMatrix(mrpt::math::CMatrixDouble44& out_HM) const;

	/** Returns the SE(2) 2x2 rotation matrix */
	void getRotationMatrix(mrpt::math::CMatrixDouble22& R) const;
	/** Returns the equivalent SE(3) 3x3 rotation matrix, with (2,2)=1. */
	void getRotationMatrix(mrpt::math::CMatrixDouble33& R) const;

	template <class MATRIX22>
	inline MATRIX22 getRotationMatrix() const
	{
		MATRIX22 R;
		getRotationMatrix(R);
		return R;
	}

	/** The operator \f$ a = this \oplus D \f$ is the pose compounding operator.
	 */
	CPose2D operator+(const CPose2D& D) const;

	/** Makes \f$ this = A \oplus B \f$
	 *  \note A or B can be "this" without problems.  */
	void composeFrom(const CPose2D& A, const CPose2D& B);

	/** The operator \f$ a = this \oplus D \f$ is the pose compounding operator.
	 */
	CPose3D operator+(const CPose3D& D) const;

	/** The operator \f$ u' = this \oplus u \f$ is the pose/point compounding
	 * operator.  */
	CPoint2D operator+(const CPoint2D& u) const;

	/** 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 composePoint(double lx, double ly, double& gx, double& gy) const;

	/** overload \f$ G = P \oplus L \f$ with G and L being 2D points and P this
	 * 2D pose */
	void composePoint(
		const mrpt::math::TPoint2D& l, mrpt::math::TPoint2D& g) const;

	/// \overload
	mrpt::math::TPoint3D composePoint(const mrpt::math::TPoint3D& l) const;

	/** 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 composePoint(
		const mrpt::math::TPoint3D& l, mrpt::math::TPoint3D& g) const;
	/** overload (the "z" coordinate remains unmodified) */
	void composePoint(
		double lx, double ly, double lz, double& gx, double& gy,
		double& gz) const;

	/**  Computes the 2D point L such as \f$ L = G \ominus this \f$. \sa
	 * composePoint, composeFrom */
	void inverseComposePoint(
		const double gx, const double gy, double& lx, double& ly) const;
	/** \overload */
	void inverseComposePoint(
		const mrpt::math::TPoint2D& g, mrpt::math::TPoint2D& l) const;
	/** \overload */
	mrpt::math::TPoint2D inverseComposePoint(
		const mrpt::math::TPoint2D& g) const;

	/** The operator \f$ u' = this \oplus u \f$ is the pose/point compounding
	 * operator. */
	CPoint3D operator+(const CPoint3D& u) const;

	/**  Makes \f$ this = A \ominus B \f$ this method is slightly more efficient
	 * than "this= A - B;" since it avoids the temporary object.
	 *  \note A or B can be "this" without problems.
	 * \sa composeFrom, composePoint
	 */
	void inverseComposeFrom(const CPose2D& A, const CPose2D& B);

	/** Convert this pose into its inverse, saving the result in itself. \sa
	 * operator- */
	void inverse();

	/** Compute \f$ RET = this \ominus b \f$  */
	inline CPose2D operator-(const CPose2D& b) const
	{
		CPose2D ret(UNINITIALIZED_POSE);
		ret.inverseComposeFrom(*this, b);
		return ret;
	}

	/** The operator \f$ a \ominus b \f$ is the pose inverse compounding
	 * operator. */
	CPose3D operator-(const CPose3D& b) const;

	/** Scalar sum of components: This is diferent from poses
	 *    composition, which is implemented as "+" operators in "CPose" derived
	 * classes.
	 */
	void AddComponents(const CPose2D& p);

	/** Scalar multiplication.
	 */
	void operator*=(const double s);

	/** Make \f$ this = this \oplus b \f$  */
	CPose2D& operator+=(const CPose2D& b);

	/** Forces "phi" to be in the range [-pi,pi];
	 */
	void normalizePhi();

	/** Return the opposite of the current pose instance by taking the negative
	 * of all its components \a individually
	 */
	CPose2D getOppositeScalar() const;

	/** Returns a human-readable textual representation of the object (eg: "[x y
	 * yaw]", yaw in degrees)
	 * \sa fromString
	 */
	std::string asString() const override;

	/** Set the current object value from a string generated by 'asString' (eg:
	 * "[0.02 1.04 -0.8]" )
	 * \sa asString
	 * \exception std::exception On invalid format
	 */
	void fromString(const std::string& s);
	/** Same as fromString, but without requiring the square brackets in the
	 * string */
	void fromStringRaw(const std::string& s);

	static CPose2D FromString(const std::string& s)
	{
		CPose2D o;
		o.fromString(s);
		return o;
	}

	inline double operator[](unsigned int i) const
	{
		switch (i)
		{
			case 0: return m_coords[0];
			case 1: return m_coords[1];
			case 2: return m_phi;
			default:
				throw std::runtime_error(
					"CPose2D::operator[]: Index of bounds.");
		}
	}
	inline double& operator[](unsigned int i)
	{
		switch (i)
		{
			case 0: return m_coords[0];
			case 1: return m_coords[1];
			case 2: return m_phi;
			default:
				throw std::runtime_error(
					"CPose2D::operator[]: Index of bounds.");
		}
	}

	/** makes: this = p (+) this */
	inline void changeCoordinatesReference(const CPose2D& p)
	{
		composeFrom(p, CPose2D(*this));
	}

	/** Returns the 2D distance from this pose/point to a 2D pose using the
	 * Frobenius distance. */
	double distance2DFrobeniusTo(const CPose2D& p) const;

	/** Used to emulate CPosePDF types, for example, in
	 * mrpt::graphs::CNetworkOfPoses */
	using type_value = CPose2D;
	enum
	{
		is_3D_val = 0
	};
	static constexpr bool is_3D() { return is_3D_val != 0; }
	enum
	{
		rotation_dimensions = 2
	};
	enum
	{
		is_PDF_val = 0
	};
	static constexpr bool is_PDF() { return is_PDF_val != 0; }
	inline const type_value& getPoseMean() const { return *this; }
	inline type_value& getPoseMean() { return *this; }
	void setToNaN() override;

	/** @name STL-like methods and typedefs
	   @{   */
	/** The type of the elements */
	using value_type = double;
	using reference = double&;
	using const_reference = double;
	using size_type = std::size_t;
	using difference_type = std::ptrdiff_t;

	// size is constant
	enum
	{
		static_size = 3
	};
	static constexpr size_type size() { return static_size; }
	static constexpr bool empty() { return false; }
	static constexpr size_type max_size() { return static_size; }
	static inline void resize(size_t n)
	{
		if (n != static_size)
			throw std::logic_error(format(
				"Try to change the size of CPose2D to %u.",
				static_cast<unsigned>(n)));
	}

	/** @} */

};	// End of class def.

std::ostream& operator<<(std::ostream& o, const CPose2D& p);

/** Unary - operator: return the inverse pose "-p" (Note that is NOT the same
 * than a pose with negative x y phi) \sa CPose2D::inverse() */
CPose2D operator-(const CPose2D& p);

/** Compose a 2D point from a new coordinate base given by a 2D pose. */
mrpt::math::TPoint2D operator+(
	const CPose2D& pose, const mrpt::math::TPoint2D& pnt);

bool operator==(const CPose2D& p1, const CPose2D& p2);
bool operator!=(const CPose2D& p1, const CPose2D& p2);

}  // namespace mrpt::poses
