using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;

public class SkinnedMeshFix : EditorWindow
{
	private const char _splitChar = '/';
	
	[MenuItem("ProTool/Skinned Mesh Fix")]
	public static void ShowWindow()
	{
		GetWindow<SkinnedMeshFix>();
	}

	private GameObject _source;
	private GameObject _target;

	private void OnGUI()
	{
		GameObjectField("Correct Sample", ref _source);
		GUILayout.Space(5f);
		GameObjectField("To Fix", ref _target);
		GUILayout.Space(5f);
		if (GUILayout.Button("Fix Item", GUILayout.Width(200f)))
		{
			if (_source == null || _target == null)
				Debug.LogError("No gameObject to fix or the source is not set!");
			else
			{
				var sourceRenderer = _source.GetComponentInChildren<SkinnedMeshRenderer>();
				var targetRenderer = _target.GetComponentInChildren<SkinnedMeshRenderer>();
				var targetMesh = targetRenderer.sharedMesh;
				if (sourceRenderer == null || targetRenderer == null)
					Debug.LogError("No SkinnedMeshRenderer found on one of the gameObjects!");
				else if (targetMesh == null)
					Debug.LogError("Target gameObject has no mesh!");
				else
				{
					var boneIndices = new List<BoneIndex>();
					var error = false;
					var boneNames = new string[sourceRenderer.bones.Length];
					for (var i = 0; i < boneNames.Length; i++)
					{
						var path = GetRelativeName(_source.transform, sourceRenderer.bones[i]);
						Debug.LogWarning(path);
						var targetBone = FindBone(_target.transform, path);
						if (targetBone == null)
						{
							Debug.LogWarning("Target doesn't contain the bone " + path);
							error = true;
						}
						else
						{
							int? targetId = null;
							for (var j = 0; j < targetRenderer.bones.Length; j++)
							{
								if (targetRenderer.bones[j] == targetBone)
								{
									targetId = j;
									break;
								}
							}

							if (targetId == null)
							{
								Debug.LogError(
									string.Format("Target doesn't attach the bone {0} in SkinnedMeshRenderer!", path));
								error = true;
							}
							else
							{
								Debug.LogWarning("Bone Switch To " + i + " to " + targetId.Value);
								boneIndices.Add(new BoneIndex(i, targetId.Value));
							}
						}
						
						if (error)
							break;
					}

					if (!error)
					{
						// Fix bones
						var bones = new Transform[boneIndices.Count];
						var bindposes = new Matrix4x4[boneIndices.Count];
						for (var i = 0; i < bones.Length; i++)
						{
							var id = boneIndices[i].target;
							bones[i] = targetRenderer.bones[id];
							bindposes[i] = targetMesh.bindposes[id];
						}

						targetRenderer.bones = bones;
						foreach (var bone in targetRenderer.bones)
						{
							Debug.LogWarning(bone.GetHierarchyName());	
						}
						// Fix Mesh
						var newMesh = new Mesh
						{
							vertices = targetMesh.vertices,
							uv = targetMesh.uv,
							uv2 = targetMesh.uv2,
							uv3 = targetMesh.uv3,
							uv4 = targetMesh.uv4,
							normals = targetMesh.normals,
							tangents = targetMesh.tangents,
							indexFormat = targetMesh.indexFormat,
							bindposes = bindposes,
						};
						for (var i = 0; i < targetMesh.subMeshCount; i++)
						{
							newMesh.SetTriangles(targetMesh.GetTriangles(i), i);
						}

						var boneWeights = targetMesh.boneWeights;
						for (var i = 0; i < boneWeights.Length; i++)
						{
							var boneWeight = boneWeights[i];
							if (boneWeight.weight0 > 0)
							{
								var id = GetConvertedIndex(boneWeight.boneIndex0, boneIndices);
								if (id == null)
								{
									boneWeight.weight0 = 0;
									boneWeight.boneIndex0 = 0;
								}
								else
								{
									boneWeight.boneIndex0 = id.Value;
								}
							}

							if (boneWeight.weight1 > 0)
							{
								var id = GetConvertedIndex(boneWeight.boneIndex1, boneIndices);
								if (id == null)
								{
									boneWeight.weight1 = 0;
									boneWeight.boneIndex1 = 0;
								}
								else
								{
									boneWeight.boneIndex1 = id.Value;
								}
							}

							if (boneWeight.weight2 > 0)
							{
								var id = GetConvertedIndex(boneWeight.boneIndex2, boneIndices);
								if (id == null)
								{
									boneWeight.weight2 = 0;
									boneWeight.boneIndex2 = 0;
								}
								else
								{
									boneWeight.boneIndex2 = id.Value;
								}
							}
							
							if (boneWeight.weight3 > 0)
							{
								var id = GetConvertedIndex(boneWeight.boneIndex3, boneIndices);
								if (id == null)
								{
									boneWeight.weight3 = 0;
									boneWeight.boneIndex3 = 0;
								}
								else
								{
									boneWeight.boneIndex3 = id.Value;
								}
							}

							boneWeights[i] = boneWeight;
						}

						newMesh.boneWeights = boneWeights;
						newMesh.UploadMeshData(true);
						var path = AssetDatabase.GetAssetPath(targetMesh);
						if (string.IsNullOrEmpty(path))
						{
							error = true;
							Debug.LogError("Target mesh is not an asset in project!");
						}
						else
						{
							path = path.MoveUp().Open(Path.GetFileNameWithoutExtension(targetMesh.name) + ".asset");
							AssetDatabase.CreateAsset(newMesh, path);
							AssetDatabase.Refresh();
						}

						if (!error)
						{
							targetRenderer.sharedMesh = newMesh;
							// Prefab Utility
						}
					}
				}
			}
		}
	}

	private int? GetConvertedIndex(int index, List<BoneIndex> indexList)
	{
		var boneIndex = indexList.Find(a => a.target == index);
		if (boneIndex == null)
			return null;
		else
			return boneIndex.source;
	}
	
	private void GameObjectField(string label, ref GameObject field)
	{
		field = EditorGUILayout.ObjectField(label, field, typeof(GameObject), true) as GameObject;
	}

	// ReSharper disable once SuggestBaseTypeForParameter
	private string GetRelativeName(Transform root, Transform target)
	{
		var builder = new StringBuilder();
		builder.Append(target.name);
		while (target.parent != root)
		{
			target = target.parent;
			builder.Insert(0, target.name + _splitChar);
		}

		return builder.ToString();
	}

	private Transform FindBone(Transform root, string relativePath)
	{
		var segments = relativePath.Split(_splitChar);
		for (var i = 0; i < segments.Length; i++)
		{
			root = root.Find(segments[i]);
			if (root == null)
				break;
		}

		return root;
	}

	private class BoneIndex
	{
		public readonly int source;
		public readonly int target;

		public BoneIndex(int source, int target)
		{
			this.source = source;
			this.target = target;
		}
	}
}