#include "BoneAnimatedModel.h"
#include <regex>
#include <string>
#include <fstream>
#include <iostream>

namespace ZL
{

	int getIndexByValue(const std::string& name, const std::vector<std::string>& words)
	{
		for (int i = 0; i < words.size(); i++)
		{
			if (words[i] == name)
			{
				return i;
			}
		}

		return -1;
	}


	void BoneSystem::LoadFromFile(const std::string& fileName)
	{
		

		std::ifstream f(fileName);

		//Skip first 5 lines
		std::string tempLine;
		for (int i = 0; i < 5; i++)
		{
			std::getline(f, tempLine);
		}
		std::getline(f, tempLine);

		static const std::regex pattern_count(R"(\d+)");
		static const std::regex pattern_float(R"([-]?\d+\.\d+)");
		static const std::regex pattern_int(R"([-]?\d+)");
		static const std::regex pattern_boneChildren(R"(\'([^\']+)\')");
		static const std::regex pattern_bone_weight(R"(\'([^\']+)\'.*?([-]?\d+\.\d+))");
		
		std::smatch match;

		int numberBones;

		if (std::regex_search(tempLine, match, pattern_count)) {
			std::string number_str = match.str();
			numberBones = std::stoi(number_str);
		}
		else {
			throw std::runtime_error("No number found in the input string.");
		}

		std::vector<Bone> bones;
		std::vector<std::string> boneNames;
		std::vector<std::string> boneParentNames;
		std::unordered_map<std::string, std::vector<std::string>> boneChildren;

		bones.resize(numberBones);
		boneNames.resize(numberBones);
		boneParentNames.resize(numberBones);

		for (int i = 0; i < numberBones; i++)
		{
			std::getline(f, tempLine);
			std::string boneName = tempLine.substr(6);

			boneNames[i] = boneName;

			std::getline(f, tempLine);

			std::vector<float> floatValues;

			auto b = tempLine.cbegin();
			auto e = tempLine.cend();
			while (std::regex_search(b, e, match, pattern_float)) {
				floatValues.push_back(std::stof(match.str()));
				b = match.suffix().first;
			}

			bones[i].boneStartWorld = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
			
			
			std::getline(f, tempLine); //skip tail

			std::getline(f, tempLine); //len

			if (std::regex_search(tempLine, match, pattern_float)) {
				std::string len_str = match.str();
				bones[i].boneLength = std::stof(len_str);
			}
			else {
				throw std::runtime_error("No number found in the input string.");
			}
			//---------- matrix begin

			std::getline(f, tempLine);

			b = tempLine.cbegin();
			e = tempLine.cend();
			floatValues.clear();
			while (std::regex_search(b, e, match, pattern_float)) {
				floatValues.push_back(std::stof(match.str()));
				b = match.suffix().first;
			}

			bones[i].boneMatrixWorld.m[0] = floatValues[0];
			bones[i].boneMatrixWorld.m[0 + 1 * 3] = floatValues[1];
			bones[i].boneMatrixWorld.m[0 + 2 * 3] = floatValues[2];


			std::getline(f, tempLine);

			b = tempLine.cbegin();
			e = tempLine.cend();
			floatValues.clear();
			while (std::regex_search(b, e, match, pattern_float)) {
				floatValues.push_back(std::stof(match.str()));
				b = match.suffix().first;
			}

			bones[i].boneMatrixWorld.m[1] = floatValues[0];
			bones[i].boneMatrixWorld.m[1 + 1 * 3] = floatValues[1];
			bones[i].boneMatrixWorld.m[1 + 2 * 3] = floatValues[2];


			std::getline(f, tempLine);

			b = tempLine.cbegin();
			e = tempLine.cend();
			floatValues.clear();
			while (std::regex_search(b, e, match, pattern_float)) {
				floatValues.push_back(std::stof(match.str()));
				b = match.suffix().first;
			}

			bones[i].boneMatrixWorld.m[2] = floatValues[0];
			bones[i].boneMatrixWorld.m[2 + 1 * 3] = floatValues[1];
			bones[i].boneMatrixWorld.m[2 + 2 * 3] = floatValues[2];

			//----------- matrix end
			std::getline(f, tempLine); //parent

			if (tempLine == "  Parent: None")
			{
				bones[i].parent = -1;
			}
			else
			{
				std::string boneParent = tempLine.substr(10);
				boneParentNames[i] = boneParent;
			}

			std::getline(f, tempLine); //children

			b = tempLine.cbegin();
			e = tempLine.cend();
			while (std::regex_search(b, e, match, pattern_boneChildren)) {
				
				boneChildren[boneName].push_back(match.str(1));
				b = match.suffix().first; 
			}

		}

		//Now process all the bones:
		for (int i = 0; i < numberBones; i++)
		{
			std::string boneName = boneNames[i];
			std::string boneParent = boneParentNames[i];
			if (boneParent == "")
			{
				bones[i].parent = -1;
			}
			else
			{
				bones[i].parent = getIndexByValue(boneParent, boneNames);
			}

			for (int j = 0; j < boneChildren[boneName].size(); j++)
			{
				bones[i].children.push_back(getIndexByValue(boneChildren[boneName][j], boneNames));
			}
		}

		startBones = bones;
		currentBones = bones;

		///std::cout << "Hello!" << std::endl;
		
		std::getline(f, tempLine); //vertice count
		int numberVertices;

		if (std::regex_search(tempLine, match, pattern_count)) {
			std::string number_str = match.str();
			numberVertices = std::stoi(number_str);
		}
		else {
			throw std::runtime_error("No number found in the input string.");
		}

		std::vector<Vector3f> vertices;

		vertices.resize(numberVertices);
		for (int i = 0; i < numberVertices; i++)
		{
			std::getline(f, tempLine);

			std::vector<float> floatValues;

			auto b = tempLine.cbegin();
			auto e = tempLine.cend();
			while (std::regex_search(b, e, match, pattern_float)) {
				floatValues.push_back(std::stof(match.str()));
				b = match.suffix().first;
			}

			vertices[i] = Vector3f{floatValues[0], floatValues[1], floatValues[2]};
		}

		std::getline(f, tempLine); //triangle count
		int numberTriangles;

		if (std::regex_search(tempLine, match, pattern_count)) {
			std::string number_str = match.str();
			numberTriangles = std::stoi(number_str);
		}
		else {
			throw std::runtime_error("No number found in the input string.");
		}

		std::vector<std::array<int, 3>> triangles;

		triangles.resize(numberTriangles);
		for (int i = 0; i < numberTriangles; i++)
		{
			std::getline(f, tempLine);

			std::vector<int> intValues;

			auto b = tempLine.cbegin();
			auto e = tempLine.cend();
			while (std::regex_search(b, e, match, pattern_int)) {
				intValues.push_back(std::stoi(match.str()));
				b = match.suffix().first;
			}

			triangles[i] = { intValues[0], intValues[1], intValues[2] };
		}

		std::getline(f, tempLine);//=== Vertex Weights ===
		std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> localVerticesBoneWeight;
		localVerticesBoneWeight.resize(numberVertices);

		for (int i = 0; i < numberVertices; i++)
		{
			std::getline(f, tempLine); //skip Vertex 0:
			std::getline(f, tempLine); //vertex group count
			int boneCount;

			if (std::regex_search(tempLine, match, pattern_count)) {
				std::string number_str = match.str();
				boneCount = std::stoi(number_str);
			}
			else {
				throw std::runtime_error("No number found in the input string.");
			}

			if (boneCount > MAX_BONE_COUNT)
			{
				throw std::runtime_error("more than 5 bones");
			}

			float sumWeights = 0;

			for (int j = 0; j < boneCount; j++)
			{
				std::getline(f, tempLine); //Group: 'Bone', Weight: 0.9929084181785583
				if (std::regex_search(tempLine, match, pattern_bone_weight)) {
					// ��������� ����� (��� �������)
					std::string word = match.str(1); 
					double weight = std::stod(match.str(2)); 

					int boneNumber = getIndexByValue(word, boneNames);
					localVerticesBoneWeight[i][j].boneIndex = boneNumber;
					localVerticesBoneWeight[i][j].weight = weight;
					sumWeights += weight;
				}
				else {
					throw std::runtime_error("No match found in the input string.");
				}
			}

			//Normalize weights:
			for (int j = 0; j < boneCount; j++)
			{
				localVerticesBoneWeight[i][j].weight = localVerticesBoneWeight[i][j].weight / sumWeights;
			}



		}

		std::getline(f, tempLine);//=== Animation Keyframes ===
		std::getline(f, tempLine);//=== Bone Transforms per Keyframe ===



		std::getline(f, tempLine);
		int numberKeyFrames;

		if (std::regex_search(tempLine, match, pattern_count)) {
			std::string number_str = match.str();
			numberKeyFrames = std::stoi(number_str);
		}
		else {
			throw std::runtime_error("No number found in the input string.");
		}

		animations.resize(1);

		animations[0].keyFrames.resize(numberKeyFrames);

		for (int i = 0; i < numberKeyFrames; i++)
		{
			std::getline(f, tempLine);
			int numberFrame;

			if (std::regex_search(tempLine, match, pattern_count)) {
				std::string number_str = match.str();
				numberFrame = std::stoi(number_str);
			}
			else {
				throw std::runtime_error("No number found in the input string.");
			}

			animations[0].keyFrames[i].frame = numberFrame;

			animations[0].keyFrames[i].bones.resize(numberBones);

			for (int j = 0; j < numberBones; j++)
			{
				std::getline(f, tempLine);
				std::string boneName = tempLine.substr(8);
				int boneNumber = getIndexByValue(boneName, boneNames);
				animations[0].keyFrames[i].bones[boneNumber] = startBones[boneNumber];

				std::getline(f, tempLine); //  Location: <Vector (0.0000, 0.0000, -0.0091)>

				std::vector<float> floatValues;

				auto b = tempLine.cbegin();
				auto e = tempLine.cend();
				while (std::regex_search(b, e, match, pattern_float)) {
					floatValues.push_back(std::stof(match.str()));
					b = match.suffix().first;
				}

				animations[0].keyFrames[i].bones[boneNumber].boneStartWorld = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
			
				std::getline(f, tempLine); // Rotation
				std::getline(f, tempLine); // Matrix

				//=============== Matrix begin ==================
				std::getline(f, tempLine);


				b = tempLine.cbegin();
				e = tempLine.cend();
				floatValues.clear();
				while (std::regex_search(b, e, match, pattern_float)) {
					floatValues.push_back(std::stof(match.str()));
					b = match.suffix().first;
				}

				animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[0] = floatValues[0];
				animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[0 + 1 * 3] = floatValues[1];
				animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[0 + 2 * 3] = floatValues[2];


				std::getline(f, tempLine);
				b = tempLine.cbegin();
				e = tempLine.cend();
				floatValues.clear();
				while (std::regex_search(b, e, match, pattern_float)) {
					floatValues.push_back(std::stof(match.str()));
					b = match.suffix().first;
				}

				animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[1] = floatValues[0];
				animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[1 + 1 * 3] = floatValues[1];
				animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[1 + 2 * 3] = floatValues[2];

				std::getline(f, tempLine);
				b = tempLine.cbegin();
				e = tempLine.cend();
				floatValues.clear();
				while (std::regex_search(b, e, match, pattern_float)) {
					floatValues.push_back(std::stof(match.str()));
					b = match.suffix().first;
				}

				animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[2] = floatValues[0];
				animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[2 + 1 * 3] = floatValues[1];
				animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[2 + 2 * 3] = floatValues[2];


				std::getline(f, tempLine);// ignore last matrix line

				//=============== Matrix end ==================
			
			}

		}

		// Now let's process bone weights and vertices

		for (int i = 0; i < numberTriangles; i++)
		{
			
			mesh.PositionData.push_back(vertices[triangles[i][0]]);
			mesh.PositionData.push_back(vertices[triangles[i][1]]);
			mesh.PositionData.push_back(vertices[triangles[i][2]]);
			
			verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][0]]);
			verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][1]]);
			verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][2]]);
		}

		startMesh = mesh;
	}

	void BoneSystem::Interpolate(int frame)
	{
		int startingFrame = -1;
		for (int i = 0; i < animations[0].keyFrames.size() - 1; i++)
		{
			int oldFrame = animations[0].keyFrames[i].frame;
			int nextFrame = animations[0].keyFrames[i + 1].frame;
			if (frame >= oldFrame && frame < nextFrame)
			{
				startingFrame = i;
				break;
			}
		}

		if (startingFrame == -1)
		{
			throw std::runtime_error("Exception here");
		}

		int modifiedFrameNumber = frame - animations[0].keyFrames[startingFrame].frame;

		int diffFrames = animations[0].keyFrames[startingFrame + 1].frame - animations[0].keyFrames[startingFrame].frame;

		float t = (modifiedFrameNumber + 0.f) / diffFrames;

		std::vector<Bone>& oneFrameBones = animations[0].keyFrames[startingFrame].bones;
		std::vector<Bone>& nextFrameBones = animations[0].keyFrames[startingFrame+1].bones;

		std::vector<Matrix4f> skinningMatrixForEachBone;
		//std::vector<Matrix3f> skinningMatrixForEachBone;
		skinningMatrixForEachBone.resize(currentBones.size());

		
		for (int i = 0; i < currentBones.size(); i++)
		{
			currentBones[i].boneStartWorld.v[0] = oneFrameBones[i].boneStartWorld.v[0] + t * (nextFrameBones[i].boneStartWorld.v[0] - oneFrameBones[i].boneStartWorld.v[0]);
			currentBones[i].boneStartWorld.v[1] = oneFrameBones[i].boneStartWorld.v[1] + t * (nextFrameBones[i].boneStartWorld.v[1] - oneFrameBones[i].boneStartWorld.v[1]);
			currentBones[i].boneStartWorld.v[2] = oneFrameBones[i].boneStartWorld.v[2] + t * (nextFrameBones[i].boneStartWorld.v[2] - oneFrameBones[i].boneStartWorld.v[2]);
			
			Vector4f q1 = MatrixToQuat(oneFrameBones[i].boneMatrixWorld);
			Vector4f q2 = MatrixToQuat(nextFrameBones[i].boneMatrixWorld);
			Vector4f q1_norm = q1.normalized();
			Vector4f q2_norm = q2.normalized();

			Vector4f result = slerp(q1_norm, q2_norm, t);

			currentBones[i].boneMatrixWorld = QuatToMatrix(result);

			//skinningMatrixForEachBone[i] = MultMatrixMatrix(currentBones[i].boneMatrixWorld, InverseMatrix(animations[0].keyFrames[0].bones[i].boneMatrixWorld));
			
			Matrix4f currentBoneMatrixWorld4 = MakeMatrix4x4(currentBones[i].boneMatrixWorld, currentBones[i].boneStartWorld);
			Matrix4f startBoneMatrixWorld4 = MakeMatrix4x4(animations[0].keyFrames[0].bones[i].boneMatrixWorld, animations[0].keyFrames[0].bones[i].boneStartWorld);
			Matrix4f inverstedStartBoneMatrixWorld4 = InverseMatrix(startBoneMatrixWorld4);

			skinningMatrixForEachBone[i] = MultMatrixMatrix(currentBoneMatrixWorld4, inverstedStartBoneMatrixWorld4);
			
		}
		
		
		/*
		for (int i = 0; i < currentBones.size(); i++)
		{
			currentBones[i].boneStartWorld = oneFrameBones[i].boneStartWorld;
			currentBones[i].boneMatrixWorld = oneFrameBones[i].boneMatrixWorld;
			Matrix4f currentBoneMatrixWorld4 = MakeMatrix4x4(currentBones[i].boneMatrixWorld, currentBones[i].boneStartWorld);
			Matrix4f startBoneMatrixWorld4 = MakeMatrix4x4(animations[0].keyFrames[0].bones[i].boneMatrixWorld, animations[0].keyFrames[0].bones[i].boneStartWorld);
			Matrix4f inverstedStartBoneMatrixWorld4 = InverseMatrix(startBoneMatrixWorld4);
			skinningMatrixForEachBone[i] = MultMatrixMatrix(currentBoneMatrixWorld4, inverstedStartBoneMatrixWorld4);

		}
		*/
		for (int i = 0; i < mesh.PositionData.size(); i++)
		{
			Vector4f originalPos = { 
				startMesh.PositionData[i].v[0], 
				startMesh.PositionData[i].v[1], 
				startMesh.PositionData[i].v[2], 1.0};

			Vector4f finalPos = Vector4f{0.f, 0.f, 0.f, 0.f};

			bool vMoved = false;
			//Vector3f finalPos = Vector3f{ 0.f, 0.f, 0.f };

			for (int j = 0; j < MAX_BONE_COUNT; j++)
			{
				if (verticesBoneWeight[i][j].weight != 0)
				{
					vMoved = true;
					//finalPos = finalPos + MultVectorMatrix(originalPos, skinningMatrixForEachBone[verticesBoneWeight[i][j].boneIndex]) * verticesBoneWeight[i][j].weight;
					finalPos = finalPos + MultMatrixVector(skinningMatrixForEachBone[verticesBoneWeight[i][j].boneIndex], originalPos) * verticesBoneWeight[i][j].weight;
				}
			}

			if (abs(finalPos.v[0] - originalPos.v[0]) > 1 || abs(finalPos.v[1] - originalPos.v[1]) > 1 || abs(finalPos.v[2] - originalPos.v[2]) > 1)
			{
//				std::cout << "Hello!" << std::endl;
			}

			if (!vMoved)
			{
//				std::cout << "Hello!" << std::endl;
			}

			mesh.PositionData[i].v[0] = finalPos.v[0];
			mesh.PositionData[i].v[1] = finalPos.v[1];
			mesh.PositionData[i].v[2] = finalPos.v[2];
		}




	}


}