//
// Copyright(c) 2016-2017 benikabocha.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//

#ifndef SABA_MODEL_PMXFILE_H_
#define SABA_MODEL_PMXFILE_H_

#include "MMDFileString.h"

#include <cstdint>
#include <string>
#include <vector>

#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <glm/gtc/quaternion.hpp>

namespace saba
{
	template <size_t Size>
	using PMXString = MMDFileString<Size>;

	struct PMXHeader
	{
		PMXString<4>	m_magic;
		float			m_version;

		uint8_t	m_dataSize;

		uint8_t	m_encode;	//0:UTF16 1:UTF8
		uint8_t	m_addUVNum;

		uint8_t	m_vertexIndexSize;
		uint8_t	m_textureIndexSize;
		uint8_t	m_materialIndexSize;
		uint8_t	m_boneIndexSize;
		uint8_t	m_morphIndexSize;
		uint8_t	m_rigidbodyIndexSize;
	};

	struct PMXInfo
	{
		std::string	m_modelName;
		std::string	m_englishModelName;
		std::string	m_comment;
		std::string	m_englishComment;
	};

	/*
	BDEF1
	m_boneIndices[0]

	BDEF2
	m_boneIndices[0-1]
	m_boneWeights[0]

	BDEF4
	m_boneIndices[0-3]
	m_boneWeights[0-3]

	SDEF
	m_boneIndices[0-1]
	m_boneWeights[0]
	m_sdefC
	m_sdefR0
	m_sdefR1

	QDEF
	m_boneIndices[0-3]
	m_boneWeights[0-3]
	*/
	enum class PMXVertexWeight : uint8_t
	{
		BDEF1,
		BDEF2,
		BDEF4,
		SDEF,
		QDEF,
	};

	struct PMXVertex
	{
		glm::vec3	m_position;
		glm::vec3	m_normal;
		glm::vec2	m_uv;

		glm::vec4	m_addUV[4];

		PMXVertexWeight	m_weightType; // 0:BDEF1 1:BDEF2 2:BDEF4 3:SDEF 4:QDEF
		int32_t		m_boneIndices[4];
		float		m_boneWeights[4];
		glm::vec3	m_sdefC;
		glm::vec3	m_sdefR0;
		glm::vec3	m_sdefR1;

		float	m_edgeMag;
	};

	struct PMXFace
	{
		uint32_t	m_vertices[3];
	};

	struct PMXTexture
	{
		std::string m_textureName;
	};

	/*
	0x01:両面描画
	0x02:地面影
	0x04:セルフシャドウマップへの描画
	0x08:セルフシャドウの描画
	0x10:エッジ描画
	0x20:頂点カラー(※2.1拡張)
	0x40:Point描画(※2.1拡張)
	0x80:Line描画(※2.1拡張)
	*/
	enum class PMXDrawModeFlags : uint8_t
	{
		BothFace = 0x01,
		GroundShadow = 0x02,
		CastSelfShadow = 0x04,
		RecieveSelfShadow = 0x08,
		DrawEdge = 0x10,
		VertexColor = 0x20,
		DrawPoint = 0x40,
		DrawLine = 0x80,
	};

	/*
	0:無効
	1:乗算
	2:加算
	3:サブテクスチャ(追加UV1のx,yをUV参照して通常テクスチャ描画を行う)
	*/
	enum class PMXSphereMode : uint8_t
	{
		None,
		Mul,
		Add,
		SubTexture,
	};

	enum class PMXToonMode : uint8_t
	{
		Separate,	//!< 0:個別Toon
		Common,		//!< 1:共有Toon[0-9] toon01.bmp～toon10.bmp
	};

	struct PMXMaterial
	{
		std::string	m_name;
		std::string	m_englishName;

		glm::vec4	m_diffuse;
		glm::vec3	m_specular;
		float		m_specularPower;
		glm::vec3	m_ambient;

		PMXDrawModeFlags m_drawMode;

		glm::vec4	m_edgeColor;
		float		m_edgeSize;

		int32_t	m_textureIndex;
		int32_t	m_sphereTextureIndex;
		PMXSphereMode m_sphereMode;

		PMXToonMode	m_toonMode;
		int32_t		m_toonTextureIndex;

		std::string	m_memo;

		int32_t	m_numFaceVertices;
	};

	/*
	0x0001  : 接続先(PMD子ボーン指定)表示方法 -> 0:座標オフセットで指定 1:ボーンで指定

	0x0002  : 回転可能
	0x0004  : 移動可能
	0x0008  : 表示
	0x0010  : 操作可

	0x0020  : IK

	0x0080  : ローカル付与 | 付与対象 0:ユーザー変形値／IKリンク／多重付与 1:親のローカル変形量
	0x0100  : 回転付与
	0x0200  : 移動付与

	0x0400  : 軸固定
	0x0800  : ローカル軸

	0x1000  : 物理後変形
	0x2000  : 外部親変形
	*/
	enum class PMXBoneFlags : uint16_t
	{
		TargetShowMode = 0x0001,
		AllowRotate = 0x0002,
		AllowTranslate = 0x0004,
		Visible = 0x0008,
		AllowControl = 0x0010,
		IK = 0x0020,
		AppendLocal = 0x0080,
		AppendRotate = 0x0100,
		AppendTranslate = 0x0200,
		FixedAxis = 0x0400,
		LocalAxis = 0x800,
		DeformAfterPhysics = 0x1000,
		DeformOuterParent = 0x2000,
	};

	struct PMXIKLink
	{
		int32_t			m_ikBoneIndex;
		unsigned char	m_enableLimit;

		//m_enableLimitが1のときのみ
		glm::vec3	m_limitMin;	//ラジアンで表現
		glm::vec3	m_limitMax;	//ラジアンで表現
	};

	struct PMXBone
	{
		std::string	m_name;
		std::string	m_englishName;

		glm::vec3	m_position;
		int32_t		m_parentBoneIndex;
		int32_t		m_deformDepth;

		PMXBoneFlags	m_boneFlag;

		glm::vec3	m_positionOffset;	//接続先:0の場合
		int32_t		m_linkBoneIndex;	//接続先:1の場合

										//「回転付与」または「移動付与」が有効のみ
		int32_t	m_appendBoneIndex;
		float	m_appendWeight;

		//「軸固定」が有効のみ
		glm::vec3	m_fixedAxis;

		//「ローカル軸」が有効のみ
		glm::vec3	m_localXAxis;
		glm::vec3	m_localZAxis;

		//「外部親変形」が有効のみ
		int32_t	m_keyValue;

		//「IK」が有効のみ
		int32_t	m_ikTargetBoneIndex;
		int32_t	m_ikIterationCount;
		float	m_ikLimit;	//ラジアンで表現

		std::vector<PMXIKLink>	m_ikLinks;
	};


	/*
	0:グループ
	1:頂点
	2:ボーン,
	3:UV,
	4:追加UV1
	5:追加UV2
	6:追加UV3
	7:追加UV4
	8:材質
	9:フリップ(※2.1拡張)
	10:インパルス(※2.1拡張)
	*/
	enum class PMXMorphType : uint8_t
	{
		Group,
		Position,
		Bone,
		UV,
		AddUV1,
		AddUV2,
		AddUV3,
		AddUV4,
		Material,
		Flip,
		Impluse,
	};

	struct PMXMorph
	{
		std::string	m_name;
		std::string	m_englishName;

		uint8_t			m_controlPanel;	//1:眉(左下) 2:目(左上) 3:口(右上) 4:その他(右下)  | 0:システム予約
		PMXMorphType	m_morphType;

		struct PositionMorph
		{
			int32_t		m_vertexIndex;
			glm::vec3	m_position;
		};

		struct UVMorph
		{
			int32_t		m_vertexIndex;
			glm::vec4	m_uv;
		};

		struct BoneMorph
		{
			int32_t		m_boneIndex;
			glm::vec3	m_position;
			glm::quat	m_quaternion;
		};

		struct MaterialMorph
		{
			enum class OpType : uint8_t
			{
				Mul,
				Add,
			};

			int32_t		m_materialIndex;
			OpType		m_opType;	//0:乗算 1:加算
			glm::vec4	m_diffuse;
			glm::vec3	m_specular;
			float		m_specularPower;
			glm::vec3	m_ambient;
			glm::vec4	m_edgeColor;
			float		m_edgeSize;
			glm::vec4	m_textureFactor;
			glm::vec4	m_sphereTextureFactor;
			glm::vec4	m_toonTextureFactor;
		};

		struct GroupMorph
		{
			int32_t	m_morphIndex;
			float	m_weight;
		};

		struct FlipMorph
		{
			int32_t	m_morphIndex;
			float	m_weight;
		};

		struct ImpulseMorph
		{
			int32_t		m_rigidbodyIndex;
			uint8_t		m_localFlag;	//0:OFF 1:ON
			glm::vec3	m_translateVelocity;
			glm::vec3	m_rotateTorque;
		};

		std::vector<PositionMorph>	m_positionMorph;
		std::vector<UVMorph>		m_uvMorph;
		std::vector<BoneMorph>		m_boneMorph;
		std::vector<MaterialMorph>	m_materialMorph;
		std::vector<GroupMorph>		m_groupMorph;
		std::vector<FlipMorph>		m_flipMorph;
		std::vector<ImpulseMorph>	m_impulseMorph;
	};

	struct PMXDispalyFrame
	{

		std::string	m_name;
		std::string	m_englishName;

		enum class TargetType : uint8_t
		{
			BoneIndex,
			MorphIndex,
		};
		struct Target
		{
			TargetType	m_type;
			int32_t		m_index;
		};

		enum class FrameType : uint8_t
		{
			DefaultFrame,	//!< 0:通常枠
			SpecialFrame,	//!< 1:特殊枠
		};

		FrameType			m_flag;
		std::vector<Target>	m_targets;
	};

	struct PMXRigidbody
	{
		std::string	m_name;
		std::string	m_englishName;

		int32_t		m_boneIndex;
		uint8_t		m_group;
		uint16_t	m_collisionGroup;

		/*
		0:球
		1:箱
		2:カプセル
		*/
		enum class Shape : uint8_t
		{
			Sphere,
			Box,
			Capsule,
		};
		Shape		m_shape;
		glm::vec3	m_shapeSize;

		glm::vec3	m_translate;
		glm::vec3	m_rotate;	//ラジアン

		float	m_mass;
		float	m_translateDimmer;
		float	m_rotateDimmer;
		float	m_repulsion;
		float	m_friction;

		/*
		0:ボーン追従(static)
		1:物理演算(dynamic)
		2:物理演算 + Bone位置合わせ
		*/
		enum class Operation : uint8_t
		{
			Static,
			Dynamic,
			DynamicAndBoneMerge
		};
		Operation	m_op;
	};

	struct PMXJoint
	{
		std::string	m_name;
		std::string	m_englishName;

		/*
		0:バネ付6DOF
		1:6DOF
		2:P2P
		3:ConeTwist
		4:Slider
		5:Hinge
		*/
		enum class JointType : uint8_t
		{
			SpringDOF6,
			DOF6,
			P2P,
			ConeTwist,
			Slider,
			Hinge,
		};
		JointType	m_type;
		int32_t		m_rigidbodyAIndex;
		int32_t		m_rigidbodyBIndex;

		glm::vec3	m_translate;
		glm::vec3	m_rotate;

		glm::vec3	m_translateLowerLimit;
		glm::vec3	m_translateUpperLimit;
		glm::vec3	m_rotateLowerLimit;
		glm::vec3	m_rotateUpperLimit;

		glm::vec3	m_springTranslateFactor;
		glm::vec3	m_springRotateFactor;
	};

	struct PMXSoftbody
	{
		std::string	m_name;
		std::string	m_englishName;

		/*
		0:TriMesh
		1:Rope
		*/
		enum class SoftbodyType : uint8_t
		{
			TriMesh,
			Rope,
		};
		SoftbodyType	m_type;

		int32_t			m_materialIndex;

		uint8_t		m_group;
		uint16_t	m_collisionGroup;

		/*
		0x01:B-Link 作成
		0x02:クラスタ作成
		0x04: リンク交雑
		*/
		enum class SoftbodyMask : uint8_t
		{
			BLink = 0x01,
			Cluster = 0x02,
			HybridLink = 0x04,
		};
		SoftbodyMask	m_flag;

		int32_t	m_BLinkLength;
		int32_t	m_numClusters;

		float	m_totalMass;
		float	m_collisionMargin;

		/*
		1:V_TwoSided
		2:V_OneSided
		3:F_TwoSided
		4:F_OneSided
		*/
		enum class AeroModel : int32_t
		{
			kAeroModelV_TwoSided,
			kAeroModelV_OneSided,
			kAeroModelF_TwoSided,
			kAeroModelF_OneSided,
		};
		int32_t		m_aeroModel;

		//config
		float	m_VCF;
		float	m_DP;
		float	m_DG;
		float	m_LF;
		float	m_PR;
		float	m_VC;
		float	m_DF;
		float	m_MT;
		float	m_CHR;
		float	m_KHR;
		float	m_SHR;
		float	m_AHR;

		//cluster
		float	m_SRHR_CL;
		float	m_SKHR_CL;
		float	m_SSHR_CL;
		float	m_SR_SPLT_CL;
		float	m_SK_SPLT_CL;
		float	m_SS_SPLT_CL;

		//interation
		int32_t	m_V_IT;
		int32_t	m_P_IT;
		int32_t	m_D_IT;
		int32_t	m_C_IT;

		//material
		float	m_LST;
		float	m_AST;
		float	m_VST;

		struct AnchorRigidbody
		{
			int32_t		m_rigidBodyIndex;
			int32_t		m_vertexIndex;
			uint8_t	m_nearMode; //0:FF 1:ON
		};
		std::vector<AnchorRigidbody>	m_anchorRigidbodies;

		std::vector<int32_t>	m_pinVertexIndices;
	};

	struct PMXFile
	{
		PMXHeader	m_header;
		PMXInfo		m_info;

		std::vector<PMXVertex>		m_vertices;
		std::vector<PMXFace>		m_faces;
		std::vector<PMXTexture>		m_textures;
		std::vector<PMXMaterial>	m_materials;
		std::vector<PMXBone>		m_bones;
		std::vector<PMXMorph>		m_morphs;
		std::vector<PMXDispalyFrame>	m_displayFrames;
		std::vector<PMXRigidbody>	m_rigidbodies;
		std::vector<PMXJoint>		m_joints;
		std::vector<PMXSoftbody>	m_softbodies;
	};

	bool ReadPMXFile(PMXFile* pmdFile, const char* filename);
}

#endif // !SABA_MODEL_PMXFILE_H_
