#pragma once

#include <vector>
#include <string>
#include <memory>
#include <iostream>
#include <fstream>
#include "MMDCpp14.h"

namespace pmd
{
	/// ヘッダ
	class PmdHeader
	{
	public:
		/// モデル名
		std::string name;
		/// モデル名(英語)
		std::string name_english;
		/// コメント
		std::string comment;
		/// コメント(英語)
		std::string comment_english;

		bool Read(std::ifstream* stream)
		{
			char buffer[256];
			stream->read(buffer, 20);
			name = std::string(buffer);
			stream->read(buffer, 256);
			comment = std::string(buffer);
			return true;
		}

		bool ReadExtension(std::ifstream* stream)
		{
			char buffer[256];
			stream->read(buffer, 20);
			name_english = std::string(buffer);
			stream->read(buffer, 256);
			comment_english = std::string(buffer);
			return true;
		}
	};

	/// 頂点
	class PmdVertex
	{
	public:
		/// 位置
		float position[3];

		/// 法線
		float normal[3];

		/// UV座標
		float uv[2];

		/// 関連ボーンインデックス
		uint16_t bone_index[2];

		/// ボーンウェイト
		uint8_t bone_weight;

		/// エッジ不可視
		bool edge_invisible;

		bool Read(std::ifstream* stream)
		{
			stream->read((char*) position, sizeof(float) * 3);
			stream->read((char*) normal, sizeof(float) * 3);
			stream->read((char*) uv, sizeof(float) * 2);
			stream->read((char*) bone_index, sizeof(uint16_t) * 2);
			stream->read((char*) &bone_weight, sizeof(uint8_t));
			stream->read((char*) &edge_invisible, sizeof(uint8_t));
			return true;
		}
	};

	/// 材質
	class PmdMaterial
	{
	public:
		/// 減衰色
		float diffuse[4];
		/// 光沢度
		float power;
		/// 光沢色
		float specular[3];
		/// 環境色
		float ambient[3];
		/// トーンインデックス
		uint8_t toon_index;
		/// エッジ
		uint8_t edge_flag;
		/// インデックス数
		uint32_t index_count;
		/// テクスチャファイル名
		std::string texture_filename;
		/// スフィアファイル名
		std::string sphere_filename;

		bool Read(std::ifstream* stream)
		{
			char buffer[20];
			stream->read((char*) &diffuse, sizeof(float) * 4);
			stream->read((char*) &power, sizeof(float));
			stream->read((char*) &specular, sizeof(float) * 3);
			stream->read((char*) &ambient, sizeof(float) * 3);
			stream->read((char*) &toon_index, sizeof(uint8_t));
			stream->read((char*) &edge_flag, sizeof(uint8_t));
			stream->read((char*) &index_count, sizeof(uint32_t));
			stream->read((char*) &buffer, sizeof(char) * 20);
			char* pstar = strchr(buffer, '*');
			if (NULL == pstar)
			{
				texture_filename = std::string(buffer);
				sphere_filename.clear();
			}
			else {
				*pstar = (char)NULL;
				texture_filename = std::string(buffer);
				sphere_filename = std::string(pstar+1);
			}
			return true;
		}
	};

	enum class BoneType : uint8_t
	{
		Rotation,
		RotationAndMove,
		IkEffector,
		Unknown,
		IkEffectable,
		RotationEffectable,
		IkTarget,
		Invisible,
		Twist,
		RotationMovement
	};

	/// ボーン
	class PmdBone
	{
	public:
		/// ボーン名
		std::string name;
		/// ボーン名(英語)
		std::string name_english;
		/// 親ボーン番号
		uint16_t parent_bone_index;
		/// 末端ボーン番号
		uint16_t tail_pos_bone_index;
		/// ボーン種類
		BoneType bone_type;
		/// IKボーン番号
		uint16_t ik_parent_bone_index;
		/// ボーンのヘッドの位置
		float bone_head_pos[3];

		void Read(std::istream *stream)
		{
			char buffer[20];
			stream->read(buffer, 20);
			name = std::string(buffer);
			stream->read((char*) &parent_bone_index, sizeof(uint16_t));
			stream->read((char*) &tail_pos_bone_index, sizeof(uint16_t));
			stream->read((char*) &bone_type, sizeof(uint8_t));
			stream->read((char*) &ik_parent_bone_index, sizeof(uint16_t));
			stream->read((char*) &bone_head_pos, sizeof(float) * 3);
		}

		void ReadExpantion(std::istream *stream)
		{
			char buffer[20];
			stream->read(buffer, 20);
			name_english = std::string(buffer);
		}
	};

	/// IK
	class PmdIk
	{
	public:
		/// IKボーン番号
		uint16_t ik_bone_index;
		/// IKターゲットボーン番号
		uint16_t target_bone_index;
		/// 再帰回数
		uint16_t interations;
		/// 角度制限
		float angle_limit;
		/// 影響下ボーン番号
		std::vector<uint16_t> ik_child_bone_index;

		void Read(std::istream *stream)
		{
			stream->read((char *) &ik_bone_index, sizeof(uint16_t));
			stream->read((char *) &target_bone_index, sizeof(uint16_t));
			uint8_t ik_chain_length;
			stream->read((char*) &ik_chain_length, sizeof(uint8_t));
			stream->read((char *) &interations, sizeof(uint16_t));
			stream->read((char *) &angle_limit, sizeof(float));
			ik_child_bone_index.resize(ik_chain_length);
			for (int i = 0; i < ik_chain_length; i++)
			{
				stream->read((char *) &ik_child_bone_index[i], sizeof(uint16_t));
			}
		}
	};

	class PmdFaceVertex
	{
	public:
		int vertex_index;
		float position[3];

		void Read(std::istream *stream)
		{
			stream->read((char *) &vertex_index, sizeof(int));
			stream->read((char *) position, sizeof(float) * 3);
		}
	};

	enum class FaceCategory : uint8_t
	{
		Base,
		Eyebrow,
		Eye,
		Mouth,
		Other
	};

	class PmdFace
	{
	public:
		std::string name;
		FaceCategory type;
		std::vector<PmdFaceVertex> vertices;
		std::string name_english;

		void Read(std::istream *stream)
		{
			char buffer[20];
			stream->read(buffer, 20);
			name = std::string(buffer);
			int vertex_count;
			stream->read((char*) &vertex_count, sizeof(int));
			stream->read((char*) &type, sizeof(uint8_t));
			vertices.resize(vertex_count);
			for (int i = 0; i < vertex_count; i++)
			{
				vertices[i].Read(stream);
			}
		}

		void ReadExpantion(std::istream *stream)
		{
			char buffer[20];
			stream->read(buffer, 20);
			name_english = std::string(buffer);
		}
	};

	/// ボーン枠用の枠名
	class PmdBoneDispName
	{
	public:
		std::string bone_disp_name;
		std::string bone_disp_name_english;

		void Read(std::istream *stream)
		{
			char buffer[50];
			stream->read(buffer, 50);
			bone_disp_name = std::string(buffer);
			bone_disp_name_english.clear();
		}
		void ReadExpantion(std::istream *stream)
		{
			char buffer[50];
			stream->read(buffer, 50);
			bone_disp_name_english = std::string(buffer);
		}
	};

	class PmdBoneDisp
	{
	public:
		uint16_t bone_index;
		uint8_t bone_disp_index;

		void Read(std::istream *stream)
		{
			stream->read((char*) &bone_index, sizeof(uint16_t));
			stream->read((char*) &bone_disp_index, sizeof(uint8_t));
		}
	};

	/// 衝突形状
	enum class RigidBodyShape : uint8_t
	{
		/// 球
		Sphere = 0,
		/// 直方体
		Box = 1,
		/// カプセル
		Cpusel = 2
	};

	/// 剛体タイプ
	enum class RigidBodyType : uint8_t
	{
		/// ボーン追従
		BoneConnected = 0,
		/// 物理演算
		Physics = 1,
		/// 物理演算(Bone位置合せ)
		ConnectedPhysics = 2
	};

	/// 剛体
	class PmdRigidBody
	{
	public:
		/// 名前
		std::string name;
		/// 関連ボーン番号
		uint16_t related_bone_index;
		/// グループ番号
		uint8_t group_index;
		/// マスク
		uint16_t mask;
		/// 形状
		RigidBodyShape shape;
		/// 大きさ
		float size[3];
		/// 位置
		float position[3];
		/// 回転
		float orientation[3];
		/// 質量
		float weight;
		/// 移動ダンピング
		float linear_damping;
		/// 回転ダンピング
		float anglar_damping;
		/// 反発係数
		float restitution;
		/// 摩擦係数
		float friction;
		/// 演算方法
		RigidBodyType rigid_type;

		void Read(std::istream *stream)
		{
			char buffer[20];
			stream->read(buffer, sizeof(char) * 20);
			name = (std::string(buffer));
			stream->read((char*) &related_bone_index, sizeof(uint16_t));
			stream->read((char*) &group_index, sizeof(uint8_t));
			stream->read((char*) &mask, sizeof(uint16_t));
			stream->read((char*) &shape, sizeof(uint8_t));
			stream->read((char*) size, sizeof(float) * 3);
			stream->read((char*) position, sizeof(float) * 3);
			stream->read((char*) orientation, sizeof(float) * 3);
			stream->read((char*) &weight, sizeof(float));
			stream->read((char*) &linear_damping, sizeof(float));
			stream->read((char*) &anglar_damping, sizeof(float));
			stream->read((char*) &restitution, sizeof(float));
			stream->read((char*) &friction, sizeof(float));
			stream->read((char*) &rigid_type, sizeof(char));
		}
	};

	/// 剛体の拘束
	class PmdConstraint
	{
	public:
		/// 名前
		std::string name;
		/// 剛体Aのインデックス
		uint32_t rigid_body_index_a;
		/// 剛体Bのインデックス
		uint32_t rigid_body_index_b;
		/// 位置
		float position[3];
		/// 回転
		float orientation[3];
		/// 最小移動制限
		float linear_lower_limit[3];
		/// 最大移動制限
		float linear_upper_limit[3];
		/// 最小回転制限
		float angular_lower_limit[3];
		/// 最大回転制限
		float angular_upper_limit[3];
		/// 移動に対する復元力
		float linear_stiffness[3];
		/// 回転に対する復元力
		float angular_stiffness[3];

		void Read(std::istream *stream)
		{
			char buffer[20];
			stream->read(buffer, 20);
			name = std::string(buffer);
			stream->read((char *) &rigid_body_index_a, sizeof(uint32_t));
			stream->read((char *) &rigid_body_index_b, sizeof(uint32_t));
			stream->read((char *) position, sizeof(float) * 3);
			stream->read((char *) orientation, sizeof(float) * 3);
			stream->read((char *) linear_lower_limit, sizeof(float) * 3);
			stream->read((char *) linear_upper_limit, sizeof(float) * 3);
			stream->read((char *) angular_lower_limit, sizeof(float) * 3);
			stream->read((char *) angular_upper_limit, sizeof(float) * 3);
			stream->read((char *) linear_stiffness, sizeof(float) * 3);
			stream->read((char *) angular_stiffness, sizeof(float) * 3);
		}
	};

	/// PMDモデル
	class PmdModel
	{
	public:
		float version;
		PmdHeader header;
		std::vector<PmdVertex> vertices;
		std::vector<uint16_t> indices;
		std::vector<PmdMaterial> materials;
		std::vector<PmdBone> bones;
		std::vector<PmdIk> iks;
		std::vector<PmdFace> faces;
		std::vector<uint16_t> faces_indices;
		std::vector<PmdBoneDispName> bone_disp_name;
		std::vector<PmdBoneDisp> bone_disp;
		std::vector<std::string> toon_filenames;
		std::vector<PmdRigidBody> rigid_bodies;
		std::vector<PmdConstraint> constraints;

		static std::unique_ptr<PmdModel> LoadFromFile(const char *filename)
		{
			std::ifstream stream(filename, std::ios::binary);
			if (stream.fail())
			{
				std::cerr << "could not open \"" << filename << "\"" << std::endl;
				return nullptr;
			}
			auto result = LoadFromStream(&stream);
			stream.close();
			return result;
		}

		/// ファイルからPmdModelを生成する
		static std::unique_ptr<PmdModel> LoadFromStream(std::ifstream *stream)
		{
			auto result = std::make_unique<PmdModel>();
			char buffer[100];

			// magic
			char magic[3];
			stream->read(magic, 3);
			if (magic[0] != 'P' || magic[1] != 'm' || magic[2] != 'd')
			{
				std::cerr << "invalid file" << std::endl;
				return nullptr;
			}

			// version
			stream->read((char*) &(result->version), sizeof(float));
			if (result ->version != 1.0f)
			{
				std::cerr << "invalid version" << std::endl;
				return nullptr;
			}

			// header
			result->header.Read(stream);

			// vertices
			uint32_t vertex_num;
			stream->read((char*) &vertex_num, sizeof(uint32_t));
			result->vertices.resize(vertex_num);
			for (uint32_t i = 0; i < vertex_num; i++)
			{
				result->vertices[i].Read(stream);
			}

			// indices
			uint32_t index_num;
			stream->read((char*) &index_num, sizeof(uint32_t));
			result->indices.resize(index_num);
			for (uint32_t i = 0; i < index_num; i++)
			{
				stream->read((char*) &result->indices[i], sizeof(uint16_t));
			}

			// materials
			uint32_t material_num;
			stream->read((char*) &material_num, sizeof(uint32_t));
			result->materials.resize(material_num);
			for (uint32_t i = 0; i < material_num; i++)
			{
				result->materials[i].Read(stream);
			}

			// bones
			uint16_t bone_num;
			stream->read((char*) &bone_num, sizeof(uint16_t));
			result->bones.resize(bone_num);
			for (uint32_t i = 0; i < bone_num; i++)
			{
				result->bones[i].Read(stream);
			}

			// iks
			uint16_t ik_num;
			stream->read((char*) &ik_num, sizeof(uint16_t));
			result->iks.resize(ik_num);
			for (uint32_t i = 0; i < ik_num; i++)
			{
				result->iks[i].Read(stream);
			}

			// faces
			uint16_t face_num;
			stream->read((char*) &face_num, sizeof(uint16_t));
			result->faces.resize(face_num);
			for (uint32_t i = 0; i < face_num; i++)
			{
				result->faces[i].Read(stream);
			}

			// face frames
			uint8_t face_frame_num;
			stream->read((char*) &face_frame_num, sizeof(uint8_t));
			result->faces_indices.resize(face_frame_num);
			for (uint32_t i = 0; i < face_frame_num; i++)
			{
				stream->read((char*) &result->faces_indices[i], sizeof(uint16_t));
			}

			// bone names
			uint8_t bone_disp_num;
			stream->read((char*) &bone_disp_num, sizeof(uint8_t));
			result->bone_disp_name.resize(bone_disp_num);
			for (uint32_t i = 0; i < bone_disp_num; i++)
			{
				result->bone_disp_name[i].Read(stream);
			}

			// bone frame
			uint32_t bone_frame_num;
			stream->read((char*) &bone_frame_num, sizeof(uint32_t));
			result->bone_disp.resize(bone_frame_num);
			for (uint32_t i = 0; i < bone_frame_num; i++)
			{
				result->bone_disp[i].Read(stream);
			}

			// english name
			bool english;
			stream->read((char*) &english, sizeof(char));
			if (english)
			{
				result->header.ReadExtension(stream);
				for (uint32_t i = 0; i < bone_num; i++)
				{
					result->bones[i].ReadExpantion(stream);
				}
				for (uint32_t i = 0; i < face_num; i++)
				{
					if (result->faces[i].type == pmd::FaceCategory::Base)
					{
						continue;
					}
					result->faces[i].ReadExpantion(stream);
				}
				for (uint32_t i = 0; i < result->bone_disp_name.size(); i++)
				{
					result->bone_disp_name[i].ReadExpantion(stream);
				}
			}

			// toon textures
			if (stream->peek() == std::ios::traits_type::eof())
			{
				result->toon_filenames.clear();
			}
			else {
				result->toon_filenames.resize(10);
				for (uint32_t i = 0; i < 10; i++)
				{
					stream->read(buffer, 100);
					result->toon_filenames[i] = std::string(buffer);
				}
			}

			// physics
			if (stream->peek() == std::ios::traits_type::eof())
			{
				result->rigid_bodies.clear();
				result->constraints.clear();
			}
			else {
				uint32_t rigid_body_num;
				stream->read((char*) &rigid_body_num, sizeof(uint32_t));
				result->rigid_bodies.resize(rigid_body_num);
				for (uint32_t i = 0; i < rigid_body_num; i++)
				{
					result->rigid_bodies[i].Read(stream);
				}
				uint32_t constraint_num;
				stream->read((char*) &constraint_num, sizeof(uint32_t));
				result->constraints.resize(constraint_num);
				for (uint32_t i = 0; i < constraint_num; i++)
				{
					result->constraints[i].Read(stream);
				}
			}

			if (stream->peek() != std::ios::traits_type::eof())
			{
				std::cerr << "there is unknown data" << std::endl;
			}

			return result;
		}
	};
}