#include "stdafx.h"
#include "KaydaraFBX.h"
#include "KaydaraFBXContainer.h"

#include "File.h"
#include "Matrix.h"
#include "Path.h"
#include "BinaryWriter.h"

namespace Assets::Exporters
{
	void AddObjectConnection(KaydaraFBXNode& ConnectionsNode, uint64_t From, uint64_t To)
	{
		auto& Connection = ConnectionsNode.Children.Emplace("C");

		Connection.AddPropertyString("OO");
		Connection.AddPropertyInteger64(From);
		Connection.AddPropertyInteger64(To);
	}

	void AddObjectPropertyConnection(KaydaraFBXNode& ConnectionsNode, uint64_t From, uint64_t To, const char* PropertyName)
	{
		auto& Connection = ConnectionsNode.Children.Emplace("C");

		Connection.AddPropertyString("OP");
		Connection.AddPropertyInteger64(From);
		Connection.AddPropertyInteger64(To);
		Connection.AddPropertyString(PropertyName);
	}

	void InitializeRootNode(KaydaraFBXNode& RootNode)
	{
		RootNode.Children.Emplace("Version").AddPropertyInteger32(232);
		auto& Properties = RootNode.Children.Emplace("Properties70");

		{
			auto& Prop = Properties.Children.Emplace("P");

			Prop.AddPropertyString("Lcl Rotation");
			Prop.AddPropertyString("Lcl Rotation");
			Prop.AddPropertyString("");
			Prop.AddPropertyString("A");
			Prop.AddPropertyFloat64(-90);	// To match default Maya axis
			Prop.AddPropertyFloat64(0);
			Prop.AddPropertyFloat64(0);
		}
	}

	void InitializeMaterialTexture(KaydaraFBXNode& ObjectsNode, Material& Material, uint64_t TextureId)
	{
		auto& Texture = ObjectsNode.Children.Emplace("Texture");

		auto TextureName = Material.Name + "_c" + String("\u0000\u0001Texture", 9);

		Texture.AddPropertyInteger64(TextureId);
		Texture.AddPropertyString(TextureName);
		Texture.AddPropertyString("");

		Texture.Children.Emplace("Type").AddPropertyString("TextureVideoClip");
		Texture.Children.Emplace("Version").AddPropertyInteger32(202);
		Texture.Children.Emplace("TextureName").AddPropertyString(TextureName);

		auto& Properties = Texture.Children.Emplace("Properties70");

		{
			auto& Prop = Properties.Children.Emplace("P");

			Prop.AddPropertyString("CurrentTextureBlendMode");
			Prop.AddPropertyString("enum");
			Prop.AddPropertyString("");
			Prop.AddPropertyString("");
			Prop.AddPropertyInteger32(0);
		}

		{
			auto& Prop = Properties.Children.Emplace("P");

			Prop.AddPropertyString("UVSet");
			Prop.AddPropertyString("KString");
			Prop.AddPropertyString("");
			Prop.AddPropertyString("");
			Prop.AddPropertyString("map1");
		}

		{
			auto& Prop = Properties.Children.Emplace("P");

			Prop.AddPropertyString("UseMaterial");
			Prop.AddPropertyString("bool");
			Prop.AddPropertyString("");
			Prop.AddPropertyString("");
			Prop.AddPropertyInteger32(1);
		}

		Texture.Children.Emplace("Media").AddPropertyString(Material.Name + "_c" + String("\u0000\u0001Video", 7));

		String DiffuseMap = "";
		if (Material.Slots.ContainsKey(MaterialSlotType::Albedo))
			DiffuseMap = Material.Slots[MaterialSlotType::Albedo].first;
		else if (Material.Slots.ContainsKey(MaterialSlotType::Diffuse))
			DiffuseMap = Material.Slots[MaterialSlotType::Diffuse].first;

		Texture.Children.Emplace("FileName").AddPropertyString(DiffuseMap.Replace("\\", "/"));
		Texture.Children.Emplace("RelativeFilename").AddPropertyString(DiffuseMap);
	}

	void InitializeMeshSkinCluster(KaydaraFBXNode& ObjectsNode, KaydaraFBXNode& ConnectionsNode, uint64_t SubmeshIndex, uint64_t GeometryId, uint64_t DeformerId)
	{
		auto& SkinDeformer = ObjectsNode.Children.Emplace("Deformer");

		SkinDeformer.AddPropertyInteger64(DeformerId);
		SkinDeformer.AddPropertyString(String::Format("KoreLibMesh%02d", SubmeshIndex) + String("\u0000\u0001Deformer", 10));
		SkinDeformer.AddPropertyString("Skin");

		SkinDeformer.Children.Emplace("Version").AddPropertyInteger32(101);
		SkinDeformer.Children.Emplace("Link_DeformAcuracy").AddPropertyFloat64(50);

		AddObjectConnection(ConnectionsNode, DeformerId, GeometryId);
	}

	void InitializeMeshSubDeformer(KaydaraFBXNode& ObjectsNode, Bone& Bone, uint64_t SubmeshIndex, uint64_t BoneIndex, uint64_t DeformerId)
	{
		auto& SubDeformer = ObjectsNode.Children.Emplace("Deformer");

		SubDeformer.AddPropertyInteger64(DeformerId);
		SubDeformer.AddPropertyString(String::Format("KoreLibMesh%02d_Bone%02d", SubmeshIndex, BoneIndex) + String("\u0000\u0001SubDeformer", 13));
		SubDeformer.AddPropertyString("Cluster");

		SubDeformer.Children.Emplace("Version").AddPropertyInteger32(100);

		auto GlobalMatrix = Math::Matrix::CreateFromQuaternion(Bone.GlobalRotation());
		GlobalMatrix.SetPosition(Bone.GlobalPosition());

		auto InverseGlobalMatrix = GlobalMatrix.Inverse();

		SubDeformer.Children.Emplace("Indexes").Properties.EmplaceBack('i', nullptr, 0);
		SubDeformer.Children.Emplace("Weights").Properties.EmplaceBack('d', nullptr, 0);

		auto& Transform = SubDeformer.Children.Emplace("Transform").Properties.Emplace('d', nullptr, 0);
		auto& TransformLink = SubDeformer.Children.Emplace("TransformLink").Properties.Emplace('d', nullptr, 0);

		for (uint32_t i = 0; i < 16; i++)
		{
			Transform.AddValueFloat64(InverseGlobalMatrix.GetMatrix()[i]);
			TransformLink.AddValueFloat64(GlobalMatrix.GetMatrix()[i]);
		}
	}

	bool KaydaraFBX::ExportAnimation(const Animation& Animation, const String& Path)
	{
		return false;
	}

	bool KaydaraFBX::ExportModel(const Model& Model, const String& Path)
	{
		auto Writer = IO::BinaryWriter(IO::File::Create(Path));
		auto FileName = IO::Path::GetFileNameWithoutExtension(Path);

		KaydaraFBXDocument Document;

		auto& ObjectsNode = Document.GetRootNode("Objects");
		auto& ConnectionsNode = Document.GetRootNode("Connections");

		uint32_t SubmeshIndex = 0;
		uint32_t BoneIndex = 0;
		uint32_t MatIndex = 0;

		uint64_t UniqueId = 0xDEADC0DE;
		uint64_t ModelId = 0x10000001;
		uint64_t DeformerId = 0x80000008;
		uint64_t RootModelId = 0xC0DEDEAD;
		uint64_t RootJointsId = 0xC0DEBEAD;

		auto& RootModelNode = ObjectsNode.Children.Emplace("Model");
		auto& RootJointsNode = ObjectsNode.Children.Emplace("Model");

		RootModelNode.AddPropertyInteger64(RootModelId);
		RootModelNode.AddPropertyString(FileName + String("\u0000\u0001Model", 7));
		RootModelNode.AddPropertyString("Null");

		RootJointsNode.AddPropertyInteger64(RootJointsId);
		RootJointsNode.AddPropertyString("Joints\u0000\u0001Model", 13);
		RootJointsNode.AddPropertyString("Null");

		InitializeRootNode(RootModelNode);
		InitializeRootNode(RootJointsNode);

		Dictionary<int32_t, uint64_t> BoneIdMap;

		for (auto& Bone : Model.Bones)
		{
			auto& SkeletonNode = ObjectsNode.Children.Emplace("NodeAttribute");

			SkeletonNode.AddPropertyInteger64(ModelId);
			SkeletonNode.AddPropertyString("\u0000\u0001NodeAttribute", 15);
			SkeletonNode.AddPropertyString("LimbNode");

			auto& SkeletonNodeProps = SkeletonNode.Children.Emplace("Properties70");

			{
				auto& Prop = SkeletonNodeProps.Children.Emplace("P");

				Prop.AddPropertyString("Size");
				Prop.AddPropertyString("double");
				Prop.AddPropertyString("Number");
				Prop.AddPropertyString("");
				Prop.AddPropertyFloat64(16.5);
			}

			SkeletonNode.Children.Emplace("TypeFlags").AddPropertyString("Skeleton");

			auto& JointModelNode = ObjectsNode.Children.Emplace("Model");

			auto& Position = Bone.LocalPosition();
			auto Rotation = Bone.LocalRotation().ToEulerAngles();

			JointModelNode.AddPropertyInteger64(UniqueId);
			JointModelNode.AddPropertyString(Bone.Name() + String("\u0000\u0001Model", 7));
			JointModelNode.AddPropertyString("LimbNode");

			JointModelNode.Children.Emplace("Version").AddPropertyInteger32(232);
			auto & JointNodeProps = JointModelNode.Children.Emplace("Properties70");

			{
				auto& Prop = JointNodeProps.Children.Emplace("P");

				Prop.AddPropertyString("PreRotation");
				Prop.AddPropertyString("Vector3D");
				Prop.AddPropertyString("Vector");
				Prop.AddPropertyString("");
				Prop.AddPropertyFloat64(Rotation.X);
				Prop.AddPropertyFloat64(Rotation.Y);
				Prop.AddPropertyFloat64(Rotation.Z);
			}

			{
				auto& Prop = JointNodeProps.Children.Emplace("P");

				Prop.AddPropertyString("RotationActive");
				Prop.AddPropertyString("bool");
				Prop.AddPropertyString("");
				Prop.AddPropertyString("");
				Prop.AddPropertyInteger32(1);
			}

			{
				auto& Prop = JointNodeProps.Children.Emplace("P");

				Prop.AddPropertyString("InheritType");
				Prop.AddPropertyString("enum");
				Prop.AddPropertyString("");
				Prop.AddPropertyString("");
				Prop.AddPropertyInteger32(2);
			}

			{
				auto& Prop = JointNodeProps.Children.Emplace("P");

				Prop.AddPropertyString("ScalingMax");
				Prop.AddPropertyString("Vector3D");
				Prop.AddPropertyString("Vector");
				Prop.AddPropertyString("");
				Prop.AddPropertyFloat64(0);
				Prop.AddPropertyFloat64(0);
				Prop.AddPropertyFloat64(0);
			}

			{
				auto& Prop = JointNodeProps.Children.Emplace("P");

				Prop.AddPropertyString("DefaultAttributeIndex");
				Prop.AddPropertyString("int");
				Prop.AddPropertyString("Integer");
				Prop.AddPropertyString("");
				Prop.AddPropertyInteger32(0);
			}

			{
				auto& Prop = JointNodeProps.Children.Emplace("P");

				Prop.AddPropertyString("Lcl Translation");
				Prop.AddPropertyString("Lcl Translation");
				Prop.AddPropertyString("");
				Prop.AddPropertyString("A");
				Prop.AddPropertyFloat64(Position.X);
				Prop.AddPropertyFloat64(Position.Y);
				Prop.AddPropertyFloat64(Position.Z);
			}

			AddObjectConnection(ConnectionsNode, ModelId, UniqueId);

			auto ParentIndex = Bone.Parent();

			if (ParentIndex >= 0)
			{
				AddObjectConnection(ConnectionsNode, UniqueId, BoneIdMap[ParentIndex]);
			}
			else
			{
				AddObjectConnection(ConnectionsNode, UniqueId, RootJointsId);
			}

			BoneIdMap.Add(BoneIndex, UniqueId);

			UniqueId++;
			ModelId++;
			BoneIndex++;
		}

		Dictionary<int32_t, uint64_t> MatIdMap;

		for (auto& Mat : Model.Materials)
		{
			auto& MaterialNode = ObjectsNode.Children.Emplace("Material");

			MaterialNode.AddPropertyInteger64(UniqueId);
			MaterialNode.AddPropertyString(Mat.Name + String("\u0000\u0001Material", 10));
			MaterialNode.AddPropertyString("");

			MaterialNode.Children.Emplace("Version").AddPropertyInteger32(102);
			MaterialNode.Children.Emplace("ShadingModel").AddPropertyString("Lambert");
			MaterialNode.Children.Emplace("MultiLayer").AddPropertyInteger32(0);

			MatIdMap.Add(MatIndex, UniqueId);

			UniqueId++;
			MatIndex++;

			String DiffuseMap = "";
			if (Mat.Slots.ContainsKey(MaterialSlotType::Albedo))
				DiffuseMap = Mat.Slots[MaterialSlotType::Albedo].first;
			else if (Mat.Slots.ContainsKey(MaterialSlotType::Diffuse))
				DiffuseMap = Mat.Slots[MaterialSlotType::Diffuse].first;

			if (!String::IsNullOrWhiteSpace(DiffuseMap))
			{
				InitializeMaterialTexture(ObjectsNode, Mat, UniqueId);
				AddObjectPropertyConnection(ConnectionsNode, UniqueId, UniqueId - 1, "DiffuseColor");

				UniqueId++;
			}
		}

		for (auto& Mesh : Model.Meshes)
		{
			auto& MeshModelNode = ObjectsNode.Children.Emplace("Model");

			MeshModelNode.AddPropertyInteger64(ModelId);
			MeshModelNode.AddPropertyString(String::Format("KoreLibMesh%02d", SubmeshIndex) + String("\u0000\u0001Model", 7));
			MeshModelNode.AddPropertyString("Mesh");

			MeshModelNode.Children.Emplace("Version").AddPropertyInteger32(232);
			auto& MeshNodeProps = MeshModelNode.Children.Emplace("Properties70");

			{
				auto& MeshProp = MeshNodeProps.Children.Emplace("P");
				MeshProp.AddPropertyString("Lcl Rotation");
				MeshProp.AddPropertyString("Lcl Rotation");
				MeshProp.AddPropertyString("");
				MeshProp.AddPropertyString("A");
				MeshProp.AddPropertyFloat64(0);
				MeshProp.AddPropertyFloat64(0);
				MeshProp.AddPropertyFloat64(0);
			}

			{
				auto& MeshProp = MeshNodeProps.Children.Emplace("P");
				MeshProp.AddPropertyString("DefaultAttributeIndex");
				MeshProp.AddPropertyString("int");
				MeshProp.AddPropertyString("Integer");
				MeshProp.AddPropertyString("");
				MeshProp.AddPropertyInteger32(0);
			}

			{
				auto& MeshProp = MeshNodeProps.Children.Emplace("P");
				MeshProp.AddPropertyString("InheritType");
				MeshProp.AddPropertyString("enum");
				MeshProp.AddPropertyString("");
				MeshProp.AddPropertyString("");
				MeshProp.AddPropertyInteger32(0);
			}

			MeshModelNode.Children.Emplace("MultiLayer").AddPropertyInteger32(0);
			MeshModelNode.Children.Emplace("MultiTake").AddPropertyInteger32(0);
			MeshModelNode.Children.Emplace("Shading").AddPropertyBoolean(true);
			MeshModelNode.Children.Emplace("Culling").AddPropertyString("CullingOff");

			auto& MeshNode = ObjectsNode.Children.Emplace("Geometry");

			MeshNode.AddPropertyInteger64(UniqueId);
			MeshNode.AddPropertyString(String::Format("KoreLibMesh%02d", SubmeshIndex) + String("\u0000\u0001Geometry", 10));
			MeshNode.AddPropertyString("Mesh");

			MeshNode.Children.EmplaceBack("Properties70");
			MeshNode.Children.Emplace("GeometryVersion").AddPropertyInteger32(124);

			auto& VertexBuffer = MeshNode.Children.Emplace("Vertices").Properties.Emplace('d', nullptr, 0);

			for (auto& Vertex : Mesh.Vertices)
			{
				auto& Position = Vertex.Position();

				VertexBuffer.AddValueFloat64(Position.X);
				VertexBuffer.AddValueFloat64(Position.Y);
				VertexBuffer.AddValueFloat64(Position.Z);
			}

			auto& FaceBuffer = MeshNode.Children.Emplace("PolygonVertexIndex").Properties.Emplace('i', nullptr, 0);

			for (auto& Face : Mesh.Faces)
			{
				FaceBuffer.AddValueInteger32(Face[2]);
				FaceBuffer.AddValueInteger32(Face[1]);
				FaceBuffer.AddValueInteger32(0xffffffff ^ Face[0]);
			}

			auto& LayerNormals = MeshNode.Children.Emplace("LayerElementNormal");

			LayerNormals.AddPropertyInteger32(0);

			LayerNormals.Children.Emplace("Version").AddPropertyInteger32(101);
			LayerNormals.Children.Emplace("Name").AddPropertyString("");
			LayerNormals.Children.Emplace("MappingInformationType").AddPropertyString("ByVertice");
			LayerNormals.Children.Emplace("ReferenceInformationType").AddPropertyString("Direct");

			auto& NormalsBuffer = LayerNormals.Children.Emplace("Normals").Properties.Emplace('d', nullptr, 0);

			for (auto& Vertex : Mesh.Vertices)
			{
				auto& Normal = Vertex.Normal();

				NormalsBuffer.AddValueFloat64(Normal.X);
				NormalsBuffer.AddValueFloat64(Normal.Y);
				NormalsBuffer.AddValueFloat64(Normal.Z);
			}

			for (uint8_t i = 0; i < Mesh.Vertices.UVLayerCount(); i++)
			{
				auto& LayerUVs = MeshNode.Children.Emplace("LayerElementUV");

				LayerUVs.AddPropertyInteger32(i);

				LayerUVs.Children.Emplace("Version").AddPropertyInteger32(101);
				LayerUVs.Children.Emplace("Name").AddPropertyString(String::Format("map%d", i + 1));
				LayerUVs.Children.Emplace("MappingInformationType").AddPropertyString("ByVertice");
				LayerUVs.Children.Emplace("ReferenceInformationType").AddPropertyString("Direct");

				auto& UVsBuffer = LayerUVs.Children.Emplace("UV").Properties.Emplace('d', nullptr, 0);

				for (auto& Vertex : Mesh.Vertices)
				{
					auto& UVLayer = Vertex.UVLayers(i);

					UVsBuffer.AddValueFloat64(UVLayer.U);
					UVsBuffer.AddValueFloat64((1.0l - UVLayer.V));
				}
			}

			{
				auto& LayerMats = MeshNode.Children.Emplace("LayerElementMaterial");

				LayerMats.AddPropertyInteger32(0);

				LayerMats.Children.Emplace("Version").AddPropertyInteger32(101);
				LayerMats.Children.Emplace("Name").AddPropertyString("");
				LayerMats.Children.Emplace("MappingInformationType").AddPropertyString("AllSame");
				LayerMats.Children.Emplace("ReferenceInformationType").AddPropertyString("IndexToDirect");

				auto& IndicesBuffer = LayerMats.Children.Emplace("Materials").Properties.Emplace('i', nullptr, 0);
				IndicesBuffer.AddValueInteger32(0);
			}

			auto& LayerInfo = MeshNode.Children.Emplace("Layer");

			LayerInfo.AddPropertyInteger32(0);

			LayerInfo.Children.Emplace("Version").AddPropertyInteger32(100);
			
			{
				auto& LayerElement = LayerInfo.Children.Emplace("LayerElement");
				LayerElement.Children.Emplace("Type").AddPropertyString("LayerElementNormal");
				LayerElement.Children.Emplace("TypedIndex").AddPropertyInteger32(0);
			}

			{
				auto& LayerElement = LayerInfo.Children.Emplace("LayerElement");
				LayerElement.Children.Emplace("Type").AddPropertyString("LayerElementMaterial");
				LayerElement.Children.Emplace("TypedIndex").AddPropertyInteger32(0);
			}

			{
				auto& LayerElement = LayerInfo.Children.Emplace("LayerElement");
				LayerElement.Children.Emplace("Type").AddPropertyString("LayerElementUV");
				LayerElement.Children.Emplace("TypedIndex").AddPropertyInteger32(0);
			}

			for (uint8_t i = 1; i < Mesh.Vertices.UVLayerCount(); i++)
			{
				auto& LayerInfo = MeshNode.Children.Emplace("Layer");

				LayerInfo.AddPropertyInteger32(i);

				auto& LayerElement = LayerInfo.Children.Emplace("LayerElement");
				LayerElement.Children.Emplace("Type").AddPropertyString("LayerElementUV");
				LayerElement.Children.Emplace("TypedIndex").AddPropertyInteger32(i);
			}

			AddObjectConnection(ConnectionsNode, ModelId, RootModelId);
			AddObjectConnection(ConnectionsNode, UniqueId, ModelId);

			if (Mesh.MaterialIndices[0] >= 0)
				AddObjectConnection(ConnectionsNode, MatIdMap[Mesh.MaterialIndices[0]], ModelId);			

			InitializeMeshSkinCluster(ObjectsNode, ConnectionsNode, SubmeshIndex, UniqueId, DeformerId);

			uint64_t RootDeformer = DeformerId++;

			Dictionary<int32_t, uint64_t> SubDeformers;
			Dictionary<int32_t, KaydaraFBXNode*> SubDeformerNodes;

			for (auto& Vertex : Mesh.Vertices)
			{
				for (uint8_t i = 0; i < Vertex.WeightCount(); i++)
				{
					auto& Weight = Vertex.Weights(i);
					int32_t BoneId = Weight.Bone;

					if (!SubDeformers.ContainsKey(BoneId))
					{
						// Make new subdeformer, connect to root, and add it
						InitializeMeshSubDeformer(ObjectsNode, Model.Bones[BoneId], SubmeshIndex, BoneId, DeformerId);

						// Add a connection
						AddObjectConnection(ConnectionsNode, DeformerId, RootDeformer);
						AddObjectConnection(ConnectionsNode, BoneIdMap[BoneId], DeformerId);

						// Add it
						SubDeformers.Add(BoneId, DeformerId++);
					}				
				}
			}

			for (auto& SubDef : SubDeformers)
				SubDeformerNodes.Add(SubDef.Key(), ObjectsNode.FindByUID(SubDef.Value()));

			uint32_t VertexIndex = 0;

			for (auto& Vertex : Mesh.Vertices)
			{
				for (uint8_t i = 0; i < Vertex.WeightCount(); i++)
				{
					auto& Weight = Vertex.Weights(i);
					int32_t BoneId = Weight.Bone;

					auto& DeformerNode = SubDeformerNodes[BoneId];

					DeformerNode->Children[1].Properties[0].AddValueInteger32(VertexIndex);
					DeformerNode->Children[2].Properties[0].AddValueFloat64(Weight.Value);
				}

				VertexIndex++;
			}
			
			UniqueId++;
			SubmeshIndex++;
			ModelId++;
		}

		AddObjectConnection(ConnectionsNode, RootModelId, 0);
		AddObjectConnection(ConnectionsNode, RootJointsId, 0);

		Document.Serialize(Writer);

		return true;
	}

	imstring KaydaraFBX::ModelExtension()
	{
		return ".fbx";
	}

	imstring KaydaraFBX::AnimationExtension()
	{
		return ".fbx";
	}

	ExporterScale KaydaraFBX::ExportScale()
	{
		return ExporterScale::Default;
	}

	bool KaydaraFBX::SupportsAnimations()
	{
		return false;
	}

	bool KaydaraFBX::SupportsModels()
	{
		return true;
	}
}
