#include "BoneAnimatedModelNew.h"
#include <regex>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
#include <cstring>

namespace ZL
{
#ifdef EMSCRIPTEN
	using std::min;
	using std::max;
#endif

	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;
			}
		}

		std::cout << "Bone name not found: " << name << std::endl;
		throw std::runtime_error("Bone name not found: " + name);

		return -1;
	}

	static std::string trimRight(const std::string& s)
	{
		size_t end = s.size();
		while (end > 0 && (s[end - 1] == '\r' || s[end - 1] == '\n' || s[end - 1] == ' ' || s[end - 1] == '\t'))
			end--;
		return s.substr(0, end);
	}


	void GpuBoneData::PrepareGpuSkinningData(const std::vector<std::array<BoneWeight, MAX_BONE_COUNT>>& verticesBoneWeight)
	{
		size_t vertexCount = verticesBoneWeight.size();
		boneIndices0.resize(vertexCount);
		boneIndices1.resize(vertexCount);
		boneWeights0.resize(vertexCount);
		boneWeights1.resize(vertexCount);

		for (size_t i = 0; i < vertexCount; i++)
		{
			boneIndices0[i] = Vector4f(
				static_cast<float>(max(0, verticesBoneWeight[i][0].boneIndex)),
				static_cast<float>(max(0, verticesBoneWeight[i][1].boneIndex)),
				static_cast<float>(max(0, verticesBoneWeight[i][2].boneIndex)),
				static_cast<float>(max(0, verticesBoneWeight[i][3].boneIndex))
			);
			boneIndices1[i] = Vector2f(
				static_cast<float>(max(0, verticesBoneWeight[i][4].boneIndex)),
				static_cast<float>(max(0, verticesBoneWeight[i][5].boneIndex))
			);
			boneWeights0[i] = Vector4f(
				verticesBoneWeight[i][0].weight,
				verticesBoneWeight[i][1].weight,
				verticesBoneWeight[i][2].weight,
				verticesBoneWeight[i][3].weight
			);
			boneWeights1[i] = Vector2f(
				verticesBoneWeight[i][4].weight,
				verticesBoneWeight[i][5].weight
			);
		}

		prepared = true;
	}

	void BoneSystemNew::LoadFromFile(const std::string& fileName, const std::string& ZIPFileName)
	{
		std::ifstream filestream;
		std::istringstream zipStream;

		if (!ZIPFileName.empty())
		{
			std::vector<char> fileData = readFileFromZIP(fileName, ZIPFileName);
			std::string fileContents(fileData.begin(), fileData.end());
			zipStream.str(fileContents);
		}
		else
		{
			filestream.open(fileName);
		}

		std::istream& f = (!ZIPFileName.empty()) ? static_cast<std::istream&>(zipStream) : static_cast<std::istream&>(filestream);

		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;
		std::string tempLine;

		// ---- Armature matrix (4 lines after the header) ----
		std::getline(f, tempLine); // === Armature Matrix ===
		armatureMatrix = Matrix4f::Identity();
		for (int r = 0; r < 4; r++)
		{
			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;
			}
			if (floatValues.size() >= 4)
			{
				for (int c = 0; c < 4; c++)
					armatureMatrix.data()[r + c * 4] = floatValues[c];
			}
		}

		// ---- Bones ----
		std::getline(f, tempLine); // === Armature Bones: N
		int numberBones;
		if (std::regex_search(tempLine, match, pattern_count)) {
			numberBones = std::stoi(match.str());
		}
		else {
			throw std::runtime_error("Armature bones count not found");
		}

		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); // Bone: name
			std::string boneName = trimRight(tempLine.substr(6));
			boneNames[i] = boneName;

			std::getline(f, tempLine); // HEAD_LOCAL
			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); // TAIL_LOCAL

			std::getline(f, tempLine); // Length
			if (std::regex_search(tempLine, match, pattern_float)) {
				bones[i].boneLength = std::stof(match.str());
			}
			else {
				throw std::runtime_error("Bone length not found");
			}

			// 3x3 matrix
			for (int r = 0; r < 3; r++)
			{
				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.data()[r] = floatValues[0];
				bones[i].boneMatrixWorld.data()[r + 1 * 3] = floatValues[1];
				bones[i].boneMatrixWorld.data()[r + 2 * 3] = floatValues[2];
			}

			std::getline(f, tempLine); // Parent
			std::string parentLine = trimRight(tempLine);
			if (parentLine == "  Parent: None")
			{
				bones[i].parent = -1;
			}
			else
			{
				boneParentNames[i] = parentLine.substr(10);
			}

			std::getline(f, tempLine); // Children
			auto bc = tempLine.cbegin();
			auto ec = tempLine.cend();
			while (std::regex_search(bc, ec, match, pattern_boneChildren)) {
				boneChildren[boneName].push_back(match.str(1));
				bc = match.suffix().first;
			}
		}

		// Resolve parent/child indices
		for (int i = 0; i < numberBones; i++)
		{
			const std::string& boneName = boneNames[i];
			const std::string& boneParent = boneParentNames[i];
			if (boneParent == "" || boneParent == "None")
				bones[i].parent = -1;
			else
				bones[i].parent = getIndexByValue(boneParent, boneNames);

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

		startBones = bones;
		currentBones = bones;
		this->boneNames = boneNames;

		// ---- Multi-mesh header ----
		std::getline(f, tempLine); // === TOTAL MESHES TO EXPORT: N ===
		int numberMeshes;
		if (std::regex_search(tempLine, match, pattern_count)) {
			numberMeshes = std::stoi(match.str());
		}
		else {
			throw std::runtime_error("Total meshes count not found");
		}

		for (int meshIdx = 0; meshIdx < numberMeshes; meshIdx++)
		{
			// === Mesh Object: Name ===
			std::getline(f, tempLine);
			std::string meshHeader = trimRight(tempLine);
			// Extract mesh name: strip "=== Mesh Object: " prefix and " ===" suffix
			const std::string prefix = "=== Mesh Object: ";
			const std::string suffix = " ===";
			std::string meshName;
			if (meshHeader.size() >= prefix.size() + suffix.size() &&
				meshHeader.compare(0, prefix.size(), prefix) == 0 &&
				meshHeader.compare(meshHeader.size() - suffix.size(), suffix.size(), suffix) == 0)
			{
				meshName = meshHeader.substr(prefix.size(),
					meshHeader.size() - prefix.size() - suffix.size());
			}
			else
			{
				throw std::runtime_error("Invalid mesh header: " + meshHeader);
			}

			MeshBoneData meshData;

			// Vertices
			std::getline(f, tempLine); // ===Vertices: N
			int numberVertices;
			if (std::regex_search(tempLine, match, pattern_count)) {
				numberVertices = std::stoi(match.str());
			}
			else {
				throw std::runtime_error("Vertex count not found for mesh " + meshName);
			}

			std::vector<Vector3f> vertices(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] };
			}

			// UV coordinates
			std::getline(f, tempLine); // ===UV Coordinates:
			std::getline(f, tempLine); // Face count: M
			int numberFaces;
			if (std::regex_search(tempLine, match, pattern_count)) {
				numberFaces = std::stoi(match.str());
			}
			else {
				throw std::runtime_error("Face count not found for mesh " + meshName);
			}

			std::vector<std::array<Vector2f, 3>> uvCoords(numberFaces);
			for (int i = 0; i < numberFaces; i++)
			{
				std::getline(f, tempLine); // Face X
				std::getline(f, tempLine); // UV Count: 3
				int uvCount;
				if (std::regex_search(tempLine, match, pattern_count)) {
					uvCount = std::stoi(match.str());
				}
				else {
					throw std::runtime_error("UV count not found");
				}
				if (uvCount != 3)
					throw std::runtime_error("more than 3 uvs");

				for (int j = 0; j < 3; j++)
				{
					std::getline(f, tempLine); // UV <Vector (u, v)>
					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;
					}
					if (floatValues.size() != 2)
						throw std::runtime_error("more than 2 uvs---");
					uvCoords[i][j] = Vector2f{ floatValues[0], floatValues[1] };
				}
			}

			// Normals
			std::getline(f, tempLine); // ===Normals:
			std::vector<Vector3f> normals(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;
				}
				normals[i] = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
			}

			// Triangles
			std::getline(f, tempLine); // ===Triangles: M
			int numberTriangles;
			if (std::regex_search(tempLine, match, pattern_count)) {
				numberTriangles = std::stoi(match.str());
			}
			else {
				throw std::runtime_error("Triangle count not found for mesh " + meshName);
			}

			std::vector<std::array<int, 3>> triangles(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] };
			}

			// Vertex weights
			std::getline(f, tempLine); // === Vertex Weights (Max 5 bones per vertex) ===
			std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> localVerticesBoneWeight(numberVertices);
			for (int i = 0; i < numberVertices; i++)
			{
				std::getline(f, tempLine); // Vertex X:
				std::getline(f, tempLine); // Vertex groups: K
				int boneCount;
				if (std::regex_search(tempLine, match, pattern_count)) {
					boneCount = std::stoi(match.str());
				}
				else {
					throw std::runtime_error("Vertex group count not found");
				}
				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);
					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 = static_cast<float>(weight);
						sumWeights += static_cast<float>(weight);
					}
					else {
						throw std::runtime_error("No match found in bone weight line");
					}
				}
				for (int j = 0; j < boneCount; j++)
					localVerticesBoneWeight[i][j].weight /= sumWeights;
			}

			// Build per-triangle expanded mesh
			for (int i = 0; i < numberTriangles; i++)
			{
				meshData.mesh.PositionData.push_back(vertices[triangles[i][0]]);
				meshData.mesh.PositionData.push_back(vertices[triangles[i][1]]);
				meshData.mesh.PositionData.push_back(vertices[triangles[i][2]]);

				meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][0]]);
				meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][1]]);
				meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][2]]);

				meshData.mesh.TexCoordData.push_back(uvCoords[i][0]);
				meshData.mesh.TexCoordData.push_back(uvCoords[i][1]);
				meshData.mesh.TexCoordData.push_back(uvCoords[i][2]);
			}

			meshData.startMesh = meshData.mesh;

			meshes[meshName] = std::move(meshData);
			meshNamesOrdered.push_back(meshName);
		}

		std::cout << "Loaded " << numberMeshes << " meshes from " << fileName << std::endl;

		// ---- Animation Keyframes ----
		std::getline(f, tempLine); // === Animation Keyframes ===
		std::getline(f, tempLine); // === Bone Transforms per Keyframe ===
		std::getline(f, tempLine); // Keyframes: N
		int numberKeyFrames;
		if (std::regex_search(tempLine, match, pattern_count)) {
			numberKeyFrames = std::stoi(match.str());
		}
		else {
			throw std::runtime_error("Keyframe count not found");
		}

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

		for (int i = 0; i < numberKeyFrames; i++)
		{
			std::getline(f, tempLine); // Frame: N
			int numberFrame;
			if (std::regex_search(tempLine, match, pattern_count)) {
				numberFrame = std::stoi(match.str());
			}
			else {
				throw std::runtime_error("Frame number not found");
			}

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

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

				std::getline(f, tempLine); // Location
				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:

				for (int r = 0; r < 4; r++)
				{
					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.data()[r] = floatValues[0];
					animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[r + 1 * 4] = floatValues[1];
					animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[r + 2 * 4] = floatValues[2];
					animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[r + 3 * 4] = floatValues[3];
				}
			}
		}

		if (startBones.size() > MAX_GPU_BONES)
		{
			std::cout << "Warning: model has " << startBones.size()
				<< " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl;
		}
	}

	void BoneSystemNew::LoadFromBinaryFile(const std::string& fileName, const std::string& ZIPFileName)
	{
		std::vector<char> fileData;

		if (!ZIPFileName.empty())
		{
			fileData = readFileFromZIP(fileName, ZIPFileName);
		}
		else
		{
			std::ifstream f(fileName, std::ios::binary | std::ios::ate);
			if (!f.is_open()) throw std::runtime_error("Failed to open binary file: " + fileName);
			std::streamsize fileSize = f.tellg();
			f.seekg(0);
			fileData.resize(static_cast<size_t>(fileSize));
			f.read(fileData.data(), fileSize);
		}

		const char* ptr = fileData.data();
		const char* end = ptr + fileData.size();

		auto readRaw = [&](void* dst, size_t n) {
			if (ptr + n > end) throw std::runtime_error("Unexpected EOF in binary animation file");
			std::memcpy(dst, ptr, n);
			ptr += n;
		};
		auto readUint32 = [&]() -> uint32_t { uint32_t v; readRaw(&v, 4); return v; };
		auto readInt32  = [&]() -> int32_t  { int32_t  v; readRaw(&v, 4); return v; };
		auto readFloat  = [&]() -> float    { float    v; readRaw(&v, 4); return v; };
		auto readVec3   = [&]() -> Vector3f { return Vector3f{readFloat(), readFloat(), readFloat()}; };
		auto readVec2   = [&]() -> Vector2f { return Vector2f{readFloat(), readFloat()}; };
		auto readString = [&]() -> std::string {
			uint32_t len = readUint32();
			std::string s(len, '\0');
			if (len > 0) readRaw(&s[0], len);
			return s;
		};

		// Header
		char magic[4];
		readRaw(magic, 4);
		if (std::memcmp(magic, "BSMF", 4) != 0)
			throw std::runtime_error("Invalid multi-mesh binary animation file (bad magic)");
		uint32_t version = readUint32();
		if (version != 2)
			throw std::runtime_error("Unsupported multi-mesh binary animation file version");

		// Armature matrix (row-major in file, stored with stride-4 into Matrix4f)
		{
			float m[16];
			for (int k = 0; k < 16; k++) m[k] = readFloat();
			armatureMatrix = Matrix4f::Identity();
			for (int r = 0; r < 4; r++)
				for (int c = 0; c < 4; c++)
					armatureMatrix.data()[r + c * 4] = m[r * 4 + c];
		}

		// Bones
		uint32_t numBones = readUint32();
		std::vector<Bone> bones(numBones);

		for (uint32_t i = 0; i < numBones; i++)
		{
			bones[i].boneStartWorld = readVec3();
			bones[i].boneLength = readFloat();

			float m[9];
			for (int j = 0; j < 9; j++) m[j] = readFloat();

			bones[i].boneMatrixWorld = Matrix4f::Zero();
			for (int r = 0; r < 3; r++)
				for (int c = 0; c < 3; c++)
					bones[i].boneMatrixWorld.data()[r + c * 3] = m[r * 3 + c];

			bones[i].parent = readInt32();

			uint32_t numChildren = readUint32();
			bones[i].children.resize(numChildren);
			for (uint32_t j = 0; j < numChildren; j++)
				bones[i].children[j] = readInt32();
		}

		startBones = bones;
		currentBones = bones;

		// Bone names
		boneNames.resize(numBones);
		for (uint32_t i = 0; i < numBones; i++)
			boneNames[i] = readString();

		// Meshes
		uint32_t numMeshes = readUint32();

		for (uint32_t meshIdx = 0; meshIdx < numMeshes; meshIdx++)
		{
			std::string meshName = readString();
			MeshBoneData meshData;

			uint32_t numVertices = readUint32();
			std::vector<Vector3f> vertices(numVertices);
			for (uint32_t i = 0; i < numVertices; i++)
				vertices[i] = readVec3();

			uint32_t numFaces = readUint32();
			std::vector<std::array<Vector2f, 3>> uvCoords(numFaces);
			for (uint32_t i = 0; i < numFaces; i++)
				for (int j = 0; j < 3; j++)
					uvCoords[i][j] = readVec2();

			std::vector<Vector3f> normals(numVertices);
			for (uint32_t i = 0; i < numVertices; i++)
				normals[i] = readVec3();

			uint32_t numTriangles = readUint32();
			std::vector<std::array<int, 3>> triangles(numTriangles);
			for (uint32_t i = 0; i < numTriangles; i++)
				triangles[i] = { readInt32(), readInt32(), readInt32() };

			std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> localVerticesBoneWeight(numVertices);
			for (uint32_t i = 0; i < numVertices; i++)
			{
				uint32_t numGroups = readUint32();
				float sumWeights = 0;
				for (uint32_t j = 0; j < numGroups; j++)
				{
					int boneIdx = readInt32();
					float weight = readFloat();
					if (j < MAX_BONE_COUNT)
					{
						localVerticesBoneWeight[i][j].boneIndex = boneIdx;
						localVerticesBoneWeight[i][j].weight = weight;
						sumWeights += weight;
					}
				}
				uint32_t cap = (numGroups < MAX_BONE_COUNT) ? numGroups : MAX_BONE_COUNT;
				for (uint32_t j = 0; j < cap; j++)
					localVerticesBoneWeight[i][j].weight /= sumWeights;
			}

			for (uint32_t i = 0; i < numTriangles; i++)
			{
				meshData.mesh.PositionData.push_back(vertices[triangles[i][0]]);
				meshData.mesh.PositionData.push_back(vertices[triangles[i][1]]);
				meshData.mesh.PositionData.push_back(vertices[triangles[i][2]]);

				meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][0]]);
				meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][1]]);
				meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][2]]);

				meshData.mesh.TexCoordData.push_back(uvCoords[i][0]);
				meshData.mesh.TexCoordData.push_back(uvCoords[i][1]);
				meshData.mesh.TexCoordData.push_back(uvCoords[i][2]);
			}

			meshData.startMesh = meshData.mesh;

			meshes[meshName] = std::move(meshData);
			meshNamesOrdered.push_back(meshName);
		}

		// Animation Keyframes
		uint32_t numKeyframes = readUint32();
		animations.resize(1);
		animations[0].keyFrames.resize(numKeyframes);

		for (uint32_t i = 0; i < numKeyframes; i++)
		{
			animations[0].keyFrames[i].frame = readInt32();
			animations[0].keyFrames[i].bones.resize(numBones);

			for (uint32_t j = 0; j < numBones; j++)
			{
				animations[0].keyFrames[i].bones[j] = startBones[j];
				animations[0].keyFrames[i].bones[j].boneStartWorld = readVec3();

				float m[16];
				for (int k = 0; k < 16; k++) m[k] = readFloat();

				for (int r = 0; r < 4; r++)
					for (int c = 0; c < 4; c++)
						animations[0].keyFrames[i].bones[j].boneMatrixWorld.data()[r + c * 4] = m[r * 4 + c];
			}
		}

		if (startBones.size() > MAX_GPU_BONES)
		{
			std::cout << "Warning: model has " << startBones.size()
				<< " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl;
		}
	}

	int BoneSystemNew::findBoneIndex(const std::string& name) const
	{
		for (size_t i = 0; i < boneNames.size(); i++)
		{
			if (boneNames[i] == name) return static_cast<int>(i);
		}
		return -1;
	}

	void BoneSystemNew::Interpolate(int frame)
	{
		int startingKeyFrame = -1;
		for (int i = 0; i < static_cast<int>(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)
			{
				startingKeyFrame = i;
				break;
			}
		}

		if (startingKeyFrame == -1)
		{
			std::cout << "Exception here: frame number is out of range of keyframes. Frame: " << frame << std::endl;
			throw std::runtime_error("Exception here");
		}

		int modifiedFrameNumber = frame - animations[0].keyFrames[startingKeyFrame].frame;
		int diffFrames = animations[0].keyFrames[startingKeyFrame + 1].frame - animations[0].keyFrames[startingKeyFrame].frame;
		float t = (modifiedFrameNumber + 0.f) / diffFrames;

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

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

		for (size_t i = 0; i < currentBones.size(); i++)
		{
			currentBones[i].boneStartWorld(0) = oneFrameBones[i].boneStartWorld(0) + t * (nextFrameBones[i].boneStartWorld(0) - oneFrameBones[i].boneStartWorld(0));
			currentBones[i].boneStartWorld(1) = oneFrameBones[i].boneStartWorld(1) + t * (nextFrameBones[i].boneStartWorld(1) - oneFrameBones[i].boneStartWorld(1));
			currentBones[i].boneStartWorld(2) = oneFrameBones[i].boneStartWorld(2) + t * (nextFrameBones[i].boneStartWorld(2) - oneFrameBones[i].boneStartWorld(2));

			Matrix3f oneFrameBonesMatrix = oneFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
			Matrix3f nextFrameBonesMatrix = nextFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);

			Eigen::Quaternionf q1 = Eigen::Quaternionf(oneFrameBonesMatrix).normalized();
			Eigen::Quaternionf q2 = Eigen::Quaternionf(nextFrameBonesMatrix).normalized();
			Eigen::Quaternionf result = q1.slerp(t, q2);

			Matrix3f boneMatrixWorld3 = result.toRotationMatrix();

			currentBones[i].boneMatrixWorld = Eigen::Matrix4f::Identity();
			currentBones[i].boneMatrixWorld.block<3, 3>(0, 0) = boneMatrixWorld3;
			currentBones[i].boneMatrixWorld.block<3, 1>(0, 3) = currentBones[i].boneStartWorld;

			Matrix4f startBoneMatrixWorld4 = animations[0].keyFrames[0].bones[i].boneMatrixWorld;
			skinningMatrixForEachBone[i] = currentBones[i].boneMatrixWorld * startBoneMatrixWorld4.inverse();
		}

		for (const auto& name : meshNamesOrdered)
		{
			auto it = meshes.find(name);
			if (it == meshes.end()) continue;
			MeshBoneData& md = it->second;

			for (size_t i = 0; i < md.mesh.PositionData.size(); i++)
			{
				Vector4f originalPos = {
					md.startMesh.PositionData[i](0),
					md.startMesh.PositionData[i](1),
					md.startMesh.PositionData[i](2), 1.0f };

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

				for (int j = 0; j < MAX_BONE_COUNT; j++)
				{
					if (md.verticesBoneWeight[i][j].weight != 0)
					{
						if (md.verticesBoneWeight[i][j].boneIndex == -1)
						{
							std::cout << "Exception here: bone index is -1 but weight is > 0" << std::endl;
							throw std::runtime_error("Bones loaded incorrectly - bone index is -1 but weight is > 0");
						}
						vMoved = true;
						finalPos = finalPos + (skinningMatrixForEachBone[md.verticesBoneWeight[i][j].boneIndex] * originalPos) * md.verticesBoneWeight[i][j].weight;
					}
				}

				if (!vMoved) finalPos = originalPos;

				md.mesh.PositionData[i](0) = finalPos(0);
				md.mesh.PositionData[i](1) = finalPos(1);
				md.mesh.PositionData[i](2) = finalPos(2);
			}
		}
	}

	void MeshGpuSkinningData::prepareGpuSkinningVBOs(MeshBoneData& meshData)
	{
		if (gpuSkinningPrepared) return;

		gpuBoneData.PrepareGpuSkinningData(meshData.verticesBoneWeight);
		bindPoseMutable.AssignFrom(meshData.startMesh);

		auto& gpu = gpuBoneData;

		boneIndices0VBO = std::make_shared<VBOHolder>();
		glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer());
		glBufferData(GL_ARRAY_BUFFER, gpu.boneIndices0.size() * sizeof(Eigen::Vector4f),
			gpu.boneIndices0.data(), GL_STATIC_DRAW);

		boneIndices1VBO = std::make_shared<VBOHolder>();
		glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer());
		glBufferData(GL_ARRAY_BUFFER, gpu.boneIndices1.size() * sizeof(Eigen::Vector2f),
			gpu.boneIndices1.data(), GL_STATIC_DRAW);

		boneWeights0VBO = std::make_shared<VBOHolder>();
		glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer());
		glBufferData(GL_ARRAY_BUFFER, gpu.boneWeights0.size() * sizeof(Eigen::Vector4f),
			gpu.boneWeights0.data(), GL_STATIC_DRAW);

		boneWeights1VBO = std::make_shared<VBOHolder>();
		glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer());
		glBufferData(GL_ARRAY_BUFFER, gpu.boneWeights1.size() * sizeof(Eigen::Vector2f),
			gpu.boneWeights1.data(), GL_STATIC_DRAW);

		gpuSkinningPrepared = true;
	}

	void MeshGpuSkinningData::RenderVBO(Renderer& renderer)
	{
		CheckGlError(__FILE__, __LINE__);
		glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.positionVBO->getBuffer());
		renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
		CheckGlError(__FILE__, __LINE__);

		if (bindPoseMutable.texCoordVBO) {
			glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.texCoordVBO->getBuffer());
			renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
			CheckGlError(__FILE__, __LINE__);
		}

		if (bindPoseMutable.normalVBO) {
			glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.normalVBO->getBuffer());
			renderer.VertexAttribPointer3fv("vNormal", 0, NULL);
			CheckGlError(__FILE__, __LINE__);
		} else {
			renderer.DisableVertexAttribArray("vNormal");
			CheckGlError(__FILE__, __LINE__);
		}

		glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer());
		renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
		CheckGlError(__FILE__, __LINE__);

		glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer());
		renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
		CheckGlError(__FILE__, __LINE__);

		glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer());
		renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
		CheckGlError(__FILE__, __LINE__);

		glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer());
		renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
		CheckGlError(__FILE__, __LINE__);

		glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(bindPoseMutable.data.PositionData.size()));
	}

	void GpuSkinningShaderDataNew::prepareGpuSkinningVBOs(BoneSystemNew& model)
	{
		for (const auto& name : model.meshNamesOrdered)
		{
			auto it = model.meshes.find(name);
			if (it == model.meshes.end()) continue;
			perMesh[name].prepareGpuSkinningVBOs(it->second);
		}
	}

	void GpuSkinningShaderDataNew::RenderVBO(Renderer& renderer, const std::vector<std::string>& meshNamesOrdered)
	{
		for (const auto& name : meshNamesOrdered)
		{
			auto it = perMesh.find(name);
			if (it == perMesh.end()) continue;
			it->second.RenderVBO(renderer);
		}
	}

	void GpuSkinningShaderDataNew::ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, int frame)
	{
		int startingKeyFrame = -1;
		for (size_t i = 0; i < keyFrames.size() - 1; i++)
		{
			int oldFrame = keyFrames[i].frame;
			int nextFrame = keyFrames[i + 1].frame;
			if (frame >= oldFrame && frame < nextFrame)
			{
				startingKeyFrame = static_cast<int>(i);
				break;
			}
		}

		skinningMatrices.resize(startBones.size());

		if (startingKeyFrame == -1)
		{
			for (auto& m : skinningMatrices) m = Matrix4f::Identity();
			return;
		}

		int modifiedFrameNumber = frame - keyFrames[startingKeyFrame].frame;
		int diffFrames = keyFrames[startingKeyFrame + 1].frame - keyFrames[startingKeyFrame].frame;
		float t = (modifiedFrameNumber + 0.f) / diffFrames;

		const std::vector<Bone>& oneFrameBones = keyFrames[startingKeyFrame].bones;
		const std::vector<Bone>& nextFrameBones = keyFrames[startingKeyFrame + 1].bones;

		for (size_t i = 0; i < startBones.size(); i++)
		{
			Vector3f interpPos;
			interpPos(0) = oneFrameBones[i].boneStartWorld(0) + t * (nextFrameBones[i].boneStartWorld(0) - oneFrameBones[i].boneStartWorld(0));
			interpPos(1) = oneFrameBones[i].boneStartWorld(1) + t * (nextFrameBones[i].boneStartWorld(1) - oneFrameBones[i].boneStartWorld(1));
			interpPos(2) = oneFrameBones[i].boneStartWorld(2) + t * (nextFrameBones[i].boneStartWorld(2) - oneFrameBones[i].boneStartWorld(2));

			Matrix3f oneFrameBonesMatrix = oneFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
			Matrix3f nextFrameBonesMatrix = nextFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);

			Eigen::Quaternionf q1 = Eigen::Quaternionf(oneFrameBonesMatrix).normalized();
			Eigen::Quaternionf q2 = Eigen::Quaternionf(nextFrameBonesMatrix).normalized();
			Eigen::Quaternionf result = q1.slerp(t, q2);

			Matrix3f boneMatrixWorld3 = result.toRotationMatrix();

			Matrix4f currentBoneMatrixWorld4 = Eigen::Matrix4f::Identity();
			currentBoneMatrixWorld4.block<3, 3>(0, 0) = boneMatrixWorld3;
			currentBoneMatrixWorld4.block<3, 1>(0, 3) = interpPos;

			Matrix4f startBoneMatrixWorld4 = keyFrames[0].bones[i].boneMatrixWorld;
			skinningMatrices[i] = currentBoneMatrixWorld4 * startBoneMatrixWorld4.inverse();
		}
	}

}
