/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------

Copyright (c) 2006-2018, assimp team



All rights reserved.

Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:

* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.

* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.

* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/

#pragma once

// Header files, Qt.
#include <QMap>
#if ASSIMP_QT4_VIEWER
#	include <QtOpenGL>
#else
#	include <QOpenGLWidget>
#	include <QOpenGLFunctions>
#endif // ASSIMP_QT4_VIEWER

// Header files Assimp
#include <assimp/scene.h>

/// \class CGLView
/// Class which hold and render scene.
#if ASSIMP_QT4_VIEWER
class CGLView : public QGLWidget
#else
class CGLView : public QOpenGLWidget, protected QOpenGLFunctions
#endif // ASSIMP_QT4_VIEWER
{
	Q_OBJECT

	/**********************************/
	/************* Types **************/
	/**********************************/

private:

	/// \struct SBBox
	/// Bounding box for object.
	struct SBBox
	{
		aiVector3D Minimum;///< Minimum values of coordinates.
		aiVector3D Maximum;///< Maximum values of coordinates.
	};

	/// \struct SHelper_Mesh
	/// Helper object for fast rendering of mesh (\ref aiMesh).
	struct SHelper_Mesh
	{
		const size_t Quantity_Point;///< Quantity of points.
		const size_t Quantity_Line;///< Quantity of lines.
		const size_t Quantity_Triangle;///< Quantity of triangles.
		GLuint* Index_Point;///< Array of indices for drawing points.
		GLuint* Index_Line;///< Array of indices for drawing lines.
		GLuint* Index_Triangle;///< Array of indices for drawing triangles.

		const SBBox BBox;///< BBox of mesh.

		/// \fn explicit SHelper_Mesh(const size_t pQuantity_Point, const size_t pQuantity_Line, const size_t pQuantity_Triangle, const SBBox& pBBox = {{0, 0, 0}, {0, 0, 0}})
		/// Constructor.
		/// \param [in] pQuantity_Point - quantity of points.
		/// \param [in] pQuantity_Line - quantity of lines.
		/// \param [in] pQuantity_Triangle - quantity of triangles.
		/// \param [in] pBBox - BBox of mesh.
		explicit SHelper_Mesh(const size_t pQuantity_Point, const size_t pQuantity_Line, const size_t pQuantity_Triangle, const SBBox& pBBox = {{0, 0, 0}, {0, 0, 0}});

		/// \fn ~SHelper_Mesh()
		/// Destructor.
		~SHelper_Mesh();
	};

	/// \struct SHelper_Camera
	/// Information about position of the camera in space.
	struct SHelper_Camera
	{
		aiVector3D Position;///< Coordinates of the camera.
		aiVector3D Target;///< Target point of the camera.
		// Transformation path:
		// set Camera -> Rotation_AroundCamera -> Translation_ToScene -> Rotation_Scene -> draw Scene
		aiMatrix4x4 Rotation_AroundCamera;///< Rotation matrix which set rotation angles of the scene around camera.
		aiMatrix4x4 Rotation_Scene;///< Rotation matrix which set rotation angles of the scene around own center.
		aiVector3D Translation_ToScene;///< Translation vector from camera to the scene.

		/// \fn void SetDefault()
		/// Set default parameters of camera.
		void SetDefault();
	};

public:

	/// \enum ELightType
	/// Type of light source.
	enum class ELightType { Directional, Point, Spot };

	/// \struct SLightParameters
	/// Parameters of light source.
	struct SLightParameters
	{
		aiLightSourceType Type;///< Type of light source.

		aiColor4D Ambient;///< Ambient RGBA intensity of the light.
		aiColor4D Diffuse;///< Diffuse RGBA intensity of the light.
		aiColor4D Specular;///< Specular RGBA intensity of the light.

		union UFor
		{
			/// \struct SDirectional
			/// Parameters of directional light source.
			struct SDirectional
			{
				aiVector3D Direction;

				SDirectional() {}
			} Directional;

			/// \struct SPoint
			/// Parameters of point light source.
			struct SPoint
			{
				aiVector3D Position;
				GLfloat Attenuation_Constant;
				GLfloat Attenuation_Linear;
				GLfloat Attenuation_Quadratic;

				SPoint() {}
			} Point;

			/// \struct SSpot
			/// Parameters of spot light source.
			struct SSpot
			{
				aiVector3D Position;
				GLfloat Attenuation_Constant;
				GLfloat Attenuation_Linear;
				GLfloat Attenuation_Quadratic;
				aiVector3D Direction;
				GLfloat CutOff;

				SSpot() {}
			} Spot;

			UFor() {}
		} For;

		SLightParameters() {}
	};

	/**********************************/
	/************ Variables ***********/
	/**********************************/

private:

#if !ASSIMP_QT4_VIEWER
	// Qt5 widget has another behavior, so you must to know that you already made context are current. Yes, its a dirty hack. Better decision are welcome.
	bool mGLContext_Current;///< Widget's GL-context made current.
#endif // ASSIMP_QT4_VIEWER
	// Scene
	const aiScene* mScene = nullptr;///< Copy of pointer to scene (\ref aiScene).
	SBBox mScene_BBox;///< Bounding box of scene.
	aiVector3D mScene_Center;///< Coordinates of center of the scene.
	bool mScene_DrawBBox = false;///< Flag which control drawing scene BBox.
	bool mScene_AxesEnabled = true;///< Flag which control drawing axes of the coordinate system.
	// Meshes
	size_t mHelper_Mesh_Quantity = 0;///< Quantity of meshes in scene.
	SHelper_Mesh** mHelper_Mesh = nullptr;///< Array of pointers to helper objects for drawing mesh. Sequence of meshes are equivalent to \ref aiScene::mMeshes.
	// Cameras
	SHelper_Camera mHelper_Camera;///< Information about current camera placing in space.
	SHelper_Camera mHelper_CameraDefault;///< Information about default camera initial placing in space.
	bool mCamera_DefaultAdded = true;///< If true then scene has no defined cameras and default was added, if false - scene has defined cameras.
	GLdouble mCamera_FOVY = 45.0;///< Specifies the field of view angle, in degrees, in the y direction.
	GLdouble mCamera_Viewport_AspectRatio;///< Specifies the aspect ratio that determines the field of view in the x direction. The aspect ratio is the ratio of x (width) to y (height).
	// Lighting
	bool mLightingEnabled = false;///< If true then OpenGL lighting is enabled (glEnable(GL_LIGHTING)), if false - disabled.
	///TODO: map is goooood, but not for case when one image can be used in different materials with difference in: texture transformation, targeting of the
	/// texture (ambient or emission, or even height map), texture properties.
	QMap<QString, GLuint> mTexture_IDMap;///< Map image filenames to textures ID's.

	/**********************************/
	/************ Functions ***********/
	/**********************************/

private:

	// Why in some cases pointers are used? Because: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36566
	template<typename TArg> void AssignIfLesser(TArg* pBaseValue, const TArg pTestValue) { if(pTestValue < *pBaseValue) *pBaseValue = pTestValue; }
	template<typename TArg> void AssignIfGreater(TArg* pBaseValue, const TArg pTestValue) { if(pTestValue > *pBaseValue) *pBaseValue = pTestValue; }

	template<typename TArg> void AssignIfLesser(TArg& pBaseValue, const TArg pTestValue) { if(pTestValue < pBaseValue) pBaseValue = pTestValue; }
	template<typename TArg> void AssignIfGreater(TArg& pBaseValue, const TArg pTestValue) { if(pTestValue > pBaseValue) pBaseValue = pTestValue; }

	/// \fn void Material_Apply(const aiMaterial* pMaterial)
	/// Enable pointed material.
	/// \param [in] pMaterial - pointer to material which must be used.
	void Material_Apply(const aiMaterial* pMaterial);

	/// \fn void Matrix_NodeToRoot(const aiNode* pNode, aiMatrix4x4& pOutMatrix)
	/// Calculate matrix for transforming coordinates from pointed node to root node (read as "global coordinate system").
	/// \param [in] pNode - pointer initial node from which relative coordintaes will be taken,
	/// \param [out] pOutMatrix - matrix for transform relative coordinates in \ref pNode to coordinates in root node (\ref aiScene::mRootNode).
	void Matrix_NodeToRoot(const aiNode* pNode, aiMatrix4x4& pOutMatrix);

	/// \fn void ImportTextures()
	/// Import textures.
	/// \param [in] pScenePath - path to the file of the scene.
	void ImportTextures(const QString& pScenePath);

	/// \fn void BBox_GetForNode(const aiNode& pNode, const aiMatrix4x4& pParentNode_TransformationMatrix, SBBox& pNodeBBox, bool& pFirstAssign)
	/// Calculate BBox for pointed node. Function walk thru child nodes and apply all transformations.
	/// \param [in] pNode - reference to node for which needed BBox.
	/// \param [in] pParent_TransformationMatrix - reference to parent (parent for pNode) transformation matrix.
	/// \param [in,out] pNodeBBox - reference to where pNode BBox will be placed. It will expanded by child nodes BBoxes.
	/// \param [in] pFirstAssign - means that pNodeBBox not contain valid BBox at now and assign ('=') will used for setting new value, If
	/// false then \ref BBox_Extend will be used for setting new BBox.
	void BBox_GetForNode(const aiNode& pNode, const aiMatrix4x4& pParent_TransformationMatrix, SBBox& pNodeBBox, bool& pFirstAssign);

	/// \fn void BBox_Extend(const SBBox& pChild, SBBox& pParent)
	/// Check and if need - extend current node BBox with BBox of child node.
	/// \param [in] pChild - reference to BBox which used for extend parent BBox.
	/// \param [in.out] pParent - BBox which will be extended using child BBox.
	void BBox_Extend(const SBBox& pChild, SBBox& pParent);

	/// \fn void BBox_GetVertices(const SBBox& pBBox, aiVector3D pVertices[8])
	/// Get vertices of a parallelepiped which is described by BBox.
	/// \param [in] pBBox - input BBox.
	/// \param [out] pVertices - array of vertices.
	void BBox_GetVertices(const SBBox& pBBox, aiVector3D pVertices[8]);

	/// \fn void BBox_GetFromVertices(const aiVector3D* pVertices, const size_t pVerticesQuantity, SBBox& pBBox)
	/// Calculate BBox for vertices array.
	/// \param [in] pVertices - vertices array.
	/// \param [in] pVerticesQuantity - quantity of vertices in array. If 0 then pBBox will be assigned with {{0, 0, 0}, {0, 0, 0}}.
	/// \param [out] pBBox - calculated BBox.
	void BBox_GetFromVertices(const aiVector3D* pVertices, const size_t pVerticesQuantity, SBBox& pBBox);

	/********************************************************************/
	/************************ Logging functions *************************/
	/********************************************************************/

	/// \fn void LogInfo(const QString& pMessage)
	/// Add message with severity "Warning" to log.
	void LogInfo(const QString& pMessage);

	/// \fn void LogError(const QString& pMessage)
	/// Add message with severity "Error" to log.
	void LogError(const QString& pMessage);

	/********************************************************************/
	/************************** Draw functions **************************/
	/********************************************************************/

	/// \fn void Draw_Node(const aiNode* pNode)
	/// Apply node transformation and draw meshes assigned to this node.
	/// \param [in] pNode - pointer to node for drawing (\ref aiNode).
	void Draw_Node(const aiNode* pNode);

	/// \fn void Draw_Mesh(const size_t pMesh_Index)
	/// Draw mesh.
	/// \param [in] pMesh_Index - index of mesh which must be drawn. Index point to mesh in \ref mHelper_Mesh.
	void Draw_Mesh(const size_t pMesh_Index);

	/// \fn void Draw_BBox(const SBBox& pBBox)
	/// Draw bounding box using lines.
	/// \param [in] pBBox - bounding box for drawing.
	void Draw_BBox(const SBBox& pBBox);

	/********************************************************************/
	/*********************** Override functions ************************/
	/********************************************************************/

protected:

	/// \fn void drawCoordSystem()
	/// Draw axes of the coordinate system.
    void drawCoordSystem();

	/// \fn void initializeGL() override
	/// Override function to initialise OpenGL.
	void initializeGL() override;

	/// \fn void resizeGL(int pWidth, int pHeight) override
	/// \param [in] pWidth - new width of viewport.
	/// \param [in] pHeight - new height of viewport.
	void resizeGL(int pWidth, int pHeight) override;

	/// \fn void paintGL() override
	/// Override function for rendering.
	void paintGL() override;

public:

	/********************************************************************/
	/********************** Constructor/Destructor **********************/
	/********************************************************************/

	/// \fn explicit CGLView(QWidget* pParent)
	/// Constructor.
	/// \param [in] pParent - parent widget.
	explicit CGLView(QWidget* pParent);

	/// \fn virtual ~CGLView()
	/// Destructor.
	virtual ~CGLView();

	/********************************************************************/
	/********************* Scene control functions **********************/
	/********************************************************************/

	/// \fn void FreeScene()
	/// Free all helper objects data.
	void FreeScene();

	/// \fn void SetScene(const aiScene* pScene)
	/// Set scene for rendering.
	/// \param [in] pScene - pointer to scene.
	/// \param [in] pScenePath - path to the file of the scene.
	void SetScene(const aiScene* pScene, const QString& pScenePath);

	/// \fn void Enable_SceneBBox(const bool pEnable)
	/// Enable drawing scene bounding box.
	/// \param [in] pEnable - if true then bbox will be drawing, if false - will not be drawing.
	void Enable_SceneBBox(const bool pEnable) { mScene_DrawBBox = pEnable; }

	/// \fn void Enable_Textures(const bool pEnable)
	/// Control textures drawing.
	/// \param [in] pEnable - if true then enable textures, false - disable textures.
	void Enable_Textures(const bool pEnable);

	/// \fn void Enable_Axes(const bool pEnable)
	/// Control axes drawing.
	/// \param [in] pEnable - if true then enable axes, false - disable axes.
	void Enable_Axes(const bool pEnable) { this->mScene_AxesEnabled = pEnable; }

	/********************************************************************/
	/******************** Lighting control functions ********************/
	/********************************************************************/

	/// \fn void Lighting_Enable()
	/// Enable OpenGL lighting.
	void Lighting_Enable();

	/// \fn void Lighting_Disable()
	/// Disable OpenGL lighting.
	void Lighting_Disable();

	/// \fn void Lighting_EditSource(const size_t pLightNumber, const SLightParameters& pLightParameters)
	/// Edit light source properties.
	/// \param [in] pLightNumber - light source number. \ref aiScene::mLights.
	/// \param [in] pLightParameters - light source parameters.
	void Lighting_EditSource(const size_t pLightNumber, const SLightParameters& pLightParameters);///TODO: function set

	/// \fn void Lighting_EnableSource(const size_t pLightNumber)
	/// Enable light source.
	/// \param [in] pLightNumber - light source number. \ref aiScene::mLights.
	void Lighting_EnableSource(const size_t pLightNumber);

	///void Lighting_DisableSource(const size_t pLightNumber)
	/// Disable light source,
	/// \param [in] pLightNumber - light source number. \ref aiScene::mLights.
	void Lighting_DisableSource(const size_t pLightNumber);

	/********************************************************************/
	/******************** Cameras control functions *********************/
	/********************************************************************/

	/// \fn void Camera_Set(const size_t pCameraNumber)
	/// Set view from pointed camera.
	/// \param [in] pCamera_Index - index of the camera (\ref aiScene::mCameras).
	void Camera_Set(const size_t pCameraNumber);

	/// \fn void Camera_RotateScene(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial)
	/// Rotate scene around axisees.
	/// \param [in] pAngle_X - specifies the angle of rotation around axis oX, in degrees.
	/// \param [in] pAngle_Y - specifies the angle of rotation around axis oY, in degrees.
	/// \param [in] pAngle_Z - specifies the angle of rotation around axis oZ, in degrees.
	/// \param [in] pMatrix_Rotation_Initial - matrix from which calculates new transformation matrix. If not set (equal to nullptr) then current transformation matrix
	/// will be used.
	void Camera_RotateScene(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial = nullptr);

	/// \fn void Camera_Rotate(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial = nullptr)
	/// Rotate camera around axisees.
	/// \param [in] pAngle_X - specifies the angle of rotation around axis oX, in degrees.
	/// \param [in] pAngle_Y - specifies the angle of rotation around axis oY, in degrees.
	/// \param [in] pAngle_Z - specifies the angle of rotation around axis oZ, in degrees.
	/// \param [in] pMatrix_Rotation_Initial - matrix from which calculates new transformation matrix. If not set (equal to nullptr) then current transformation matrix
	/// will be used.
	void Camera_Rotate(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial = nullptr);

	/// \fn void Camera_Translate(const size_t pTranslate_X, const size_t pTranslate_Y, const size_t pTranslate_Z)
	/// Translate camera along axises. In local coordinates.
	/// \param [in] pTranslate_X - specifies the X coordinate of translation vector.
	/// \param [in] pTranslate_Y - specifies the Y coordinate of translation vector.
	/// \param [in] pTranslate_Z - specifies the Z coordinate of translation vector.
	void Camera_Translate(const GLfloat pTranslate_X, const GLfloat pTranslate_Y, const GLfloat pTranslate_Z);

	/// \fn void Camera_Matrix(aiMatrix4x4& pRotation_Camera, aiMatrix4x4& pRotation_Scene, aiVector3D& pTranslation_Camera)
	/// Return data about camera position in world.
	/// \param [out] pRotation_Camera - rotation matrix which set rotation angles of the scene around camera.
	/// \param [out] pRotation_Scene - rotation matrix which set rotation angles of the scene around own center.
	/// \param [out] pTranslation_Camera - translation vector from camera to the scene.
	void Camera_Matrix(aiMatrix4x4& pRotation_Camera, aiMatrix4x4& pRotation_Scene, aiVector3D& pTranslation_Camera);

signals:

	/// \fn void Paint_Finished(const size_t pPaintTime, const GLfloat pDistance)
	///< Signal. Emits when execution of \ref paintGL is end.
	/// \param [out] pPaintTime_ms - time spent for rendering, in milliseconds.
	/// \param [out] pDistance - distance between current camera and center of the scene. \sa SHelper_Camera::Translation_ToScene.
	void Paint_Finished(const size_t pPaintTime_ms, const GLfloat pDistance);

	/// \fn void SceneObject_Camera(const QString& pName)
	/// Signal. Emit for every camera found in scene. Also for default camera.
	/// \param [out] pName - name of the camera.
	void SceneObject_Camera(const QString& pName);

	/// \fn void SceneObject_LightSource(const QString& pName)
	/// Signal. Emit for every light source found in scene. Also for default light source.
	/// \param [out] pName - name of the light source.
	void SceneObject_LightSource(const QString& pName);
};// class CGLView
