#ifndef CTRANSFORM_HPP
#define CTRANSFORM_HPP

#include "../../Observer/CTransformObserver.hpp"
#include "../CAbstractComponent.hpp"
#include <memory>

#define GLM_ENABLE_EXPERIMENTAL

#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>

namespace CosmicCore
{
	/**
	* @brief Class representing the transformations of the object in the space.
	*/
	class CTransform : public CAbstractComponent {
	private:

		/**
		* @brief A 3D vector representing the position of the entity .
		*/
		glm::vec3 m_center;

		/**
		* @brief A 3D vector representing the scale of the entity on each axis.
		*/
		glm::vec3 m_scale;

		/**
		* @brief A quaternion which represent the rotation.
		*/
		glm::quat m_orientation;

		/**
		* @brief A mat4 matrix representing the translation transformation.
		*/
		glm::mat4 m_translation;

		/**
		* @brief A mat4 matrix representing the rotation transformation.
		*/
		glm::mat4 m_rotation;

		/**
		* @brief A mat4 representing the scale of the object on each axis.
		*/
		glm::mat4 m_homothety;

		/**
		* @brief Full transformation of the object.
		* result of m_translation * m_rotation * m_homothety.
		*/
		glm::mat4 m_transformation;

		/**
		* @brief a list of observer.
		*/
		std::vector<std::weak_ptr<CTransformObserver>> m_observers;

		/**
		* @brief Compute the translation matrix.
		*/
		void calcTranslation(void);

		/**
		* @brief Compute the rotation matrix.
		*/
		void calcRotation(void);

		/**
		* @brief Compute the homothety matrix.
		*/
		void calcHomothety(void);

		/**
		* @brief Compute the full transformation of the object.
		*/
		void calcTranformation(void);
	public:

		/**
		* @brief CTransform's constructor. Assign the center to (0,0,0) and the scale to (1,1,1).
		* @param[in, out] m_entity The entity to which the transform belongs.
		*/
		CTransform(CEntity& m_entity);

		/**
		* @brief CTransform's constructor.
		* @param[in, out] m_entity The entity to which the transform belongs.
		* @param[in] center A 3D vector which represent the initial position of the entity.
		* @param[in] scale A 3D vector which represent the scale of the entity on each axis.
		* @param[in] eulerAngle A 3D vector wich represent the initial rotation of the entity.
		*/
		CTransform(CEntity& m_entity, glm::vec3 center, glm::vec3 scale, glm::vec3 eulerAngle);

		/**
		* @brief Getter to the translation matrix.
		* @return A mat4 equal to m_translation.
		*/
		glm::mat4 getTranslation(void) const;

		/**
		* @brief Getter to the rotation matrix.
		* @return A mat4 equal to m_rotation.
		*/
		glm::mat4 getRotation(void) const;

		/**
		* @brief Getter to the homothety.
		* @return A mat4 equal to m_homothety .
		*/
		glm::mat4 getHomothety(void) const;

		/**
		* @brief Getter to the full transformation.
		* @return A mat4 equal to m_transformation.
		*/
		glm::mat4 getTransormation(void) const;

		/**
		* @brief Getter to the full inherited transformation.
		* Return the current entity's transformation if there is no parent or the transformation of each parent recursively times the current transformation.
		* @return A mat4 representing the actual transformation of the entity (in the global scale).
		*/
		glm::mat4 getInheritedTransformation(void) const;

		/**
		* @brief Getter to the entity's center.
		* @return The 3D vector of the position.
		*/
		glm::vec3 getCenter(void) const;

		/**
		* @brief Getter to the entity's center by its pointer.
		* @return The 3D vector of the position.
		*/
		glm::vec3* getCenterReference(void);


		/**
		* @brief Getter to the scale.
		* @return The 3D vector of the scale.
		*/
		glm::vec3 getScale(void) const;

		glm::vec3& getScalePtr(void);

		/**
		* @brief Getter to the angles of the rotation on each axis.
		* @return The 3D vector of the rotation angles, determining the orientation of the object.
		*/
		glm::vec3 getEulerAngle(void) const;


		/**
		* @brief Getter to the quaternion.
		* @return A quaternion representing the orientation of the object.
		*/
		glm::quat getOrientation(void) const;

		/**
		* @brief Setter of the position.
		* @param[in] newCenter The new position of the object.
		*/
		void setCenter(glm::vec3 newCenter);

		/**
		* @brief Setter of the uniform scale (same scale on the 3-axis).
		* @param[in] newScale The new uniform scale.
		*/
		void setUniformScale(float newScale);

		/**
		* @brief Setter of the scale.
		* @param[in] newScale The new scale on each axis (3D vector).
		*/
		void setScale(glm::vec3 newScale);

		/**
		* @brief Rotate the object on a given axis with the given angle.
		* @param[in] axis The axis on which the rotation will occur.
		* @param[in] angle The angle the rotation must reach.
		*/
		void rotate(glm::vec3 axis, float angle);

		/**
		* @brief Translate the object on the given axis with the given value on each axis.
		* @param[in] value The 3D vector of the translation.
		*/
		void forward(glm::vec3 value);

		/**
		* @brief Setter of the Angles.
		* @param[in] newEulerAngle The new orientation of the object.
		*/
		void setEulerAngle(glm::vec3 newEulerAngle);

		/**
		* @brief Setter of the orientation quaternion.
		* @param[in] newQuaternion The new orientation of the object.
		*/
		void setQuaternion(glm::quat newQuaternion);

		/**
		* @brief Add an observer on the transform. Can be from any class if the said class is child class of CTransformObserver.
		* @param[in, out] newObserver The observer to add.
		*/
		void addObserver(std::weak_ptr<CTransformObserver> newObserver);

		/**
		* @brief Remove an observer of the transform. Does nothing if the observer doesn't exist.
		* @param[in, out] obs The observer to remove.
		*/
		void removeObserver(std::weak_ptr<CTransformObserver> obs);

		nlohmann::json to_json() {
			nlohmann::json j;
			j["Position"]["x"] = m_center.x;
			j["Position"]["y"] = m_center.y;
			j["Position"]["z"] = m_center.z;

			j["Rotation"]["x"] = getEulerAngle().x;
			j["Rotation"]["y"] = getEulerAngle().y;
			j["Rotation"]["z"] = getEulerAngle().z;

			j["Scale"]["x"] = getScale().x;
			j["Scale"]["y"] = getScale().y;
			j["Scale"]["z"] = getScale().z;
			return j;
		};

		static CTransform from_json(CEntity entity,nlohmann::json& j)
		{
			CTransform t(entity);
			t.setCenter(glm::vec3(j["Position"]["x"], j["Position"]["y"], j["Position"]["z"]));
			t.setEulerAngle(glm::vec3(j["Rotation"]["x"], j["Rotation"]["y"], j["Rotation"]["z"]));
			t.setScale(glm::vec3(j["Scale"]["x"], j["Scale"]["y"], j["Scale"]["z"]));
			return t;
		};
	};
}
#endif