/******************************************************************************
 * Spine Runtimes Software License
 * Version 2.3
 * 
 * Copyright (c) 2013-2015, Esoteric Software
 * All rights reserved.
 * 
 * You are granted a perpetual, non-exclusive, non-sublicensable and
 * non-transferable license to use, install, execute and perform the Spine
 * Runtimes Software (the "Software") and derivative works solely for personal
 * or internal use. Without the written permission of Esoteric Software (see
 * Section 2 of the Spine Software License Agreement), you may not (a) modify,
 * translate, adapt or otherwise create derivative works, improvements of the
 * Software or develop new applications using the Software or (b) remove,
 * delete, alter or obscure any trademarks or any copyright, trademark, patent
 * or other intellectual property or proprietary rights notices on or in the
 * Software, including any copy thereof. Redistributions in binary or source
 * form must include this license and terms.
 * 
 * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

#pragma warning disable 0219

/*****************************************************************************
 * Spine Editor Utilities created by Mitch Thompson
 * Full irrevocable rights and permissions granted to Esoteric Software
*****************************************************************************/
#define SPINE_SKELETONANIMATOR
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
using System.Reflection;
using Spine;

namespace Spine.Unity.Editor {
	
	[InitializeOnLoad]
	public class SpineEditorUtilities : AssetPostprocessor {

		public static class Icons {
			public static Texture2D skeleton;
			public static Texture2D nullBone;
			public static Texture2D bone;
			public static Texture2D poseBones;
			public static Texture2D boneNib;
			public static Texture2D slot;
			public static Texture2D slotRoot;
			public static Texture2D skinPlaceholder;
			public static Texture2D image;
			public static Texture2D boundingBox;
			public static Texture2D mesh;
			public static Texture2D weights;
			public static Texture2D skin;
			public static Texture2D skinsRoot;
			public static Texture2D animation;
			public static Texture2D animationRoot;
			public static Texture2D spine;
			public static Texture2D _event;
			public static Texture2D constraintNib;
			public static Texture2D warning;
			public static Texture2D skeletonUtility;
			public static Texture2D hingeChain;
			public static Texture2D subMeshRenderer;
			public static Texture2D unityIcon;
			public static Texture2D controllerIcon;

			public static Mesh boneMesh {
				get {
					if (_boneMesh == null) {
						_boneMesh = new Mesh();
						_boneMesh.vertices = new Vector3[4] {
							Vector3.zero,
							new Vector3(-0.1f, 0.1f, 0),
							Vector3.up,
							new Vector3(0.1f, 0.1f, 0)
						};
						_boneMesh.uv = new Vector2[4];
						_boneMesh.triangles = new int[6] { 0, 1, 2, 2, 3, 0 };
						_boneMesh.RecalculateBounds();
						_boneMesh.RecalculateNormals();
					}

					return _boneMesh;
				}
			}

			internal static Mesh _boneMesh;

			public static Material boneMaterial {
				get {
					if (_boneMaterial == null) {
						#if UNITY_4_3
						_boneMaterial = new Material(Shader.Find("Particles/Alpha Blended"));
						_boneMaterial.SetColor("_TintColor", new Color(0.4f, 0.4f, 0.4f, 0.25f));
						#else
						_boneMaterial = new Material(Shader.Find("Spine/Bones"));
						_boneMaterial.SetColor("_Color", new Color(0.4f, 0.4f, 0.4f, 0.25f));
						#endif

					}

					return _boneMaterial;
				}
			}

			internal static Material _boneMaterial;

			public static void Initialize () {
				skeleton = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeleton.png");
				nullBone = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-null.png");
				bone = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-bone.png");
				poseBones = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-poseBones.png");
				boneNib = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-boneNib.png");
				slot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-slot.png");
				slotRoot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-slotRoot.png");
				skinPlaceholder = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skinPlaceholder.png");
				image = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-image.png");
				boundingBox = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-boundingBox.png");
				mesh = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-mesh.png");
				weights = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-weights.png");
				skin = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skinPlaceholder.png");
				skinsRoot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skinsRoot.png");
				animation = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-animation.png");
				animationRoot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-animationRoot.png");
				spine = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-spine.png");
				_event = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-event.png");
				constraintNib = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-constraintNib.png");
				warning = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-warning.png");
				skeletonUtility = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeletonUtility.png");
				hingeChain = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-hingeChain.png");
				subMeshRenderer = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-subMeshRenderer.png");

				unityIcon = EditorGUIUtility.FindTexture("SceneAsset Icon");

				controllerIcon = EditorGUIUtility.FindTexture("AnimatorController Icon");
			}
		}

		public static string editorPath = "";
		public static string editorGUIPath = "";
		static HashSet<string> assetsImportedInWrongState;
		static Dictionary<int, GameObject> skeletonRendererTable;
		static Dictionary<int, SkeletonUtilityBone> skeletonUtilityBoneTable;
		static Dictionary<int, BoundingBoxFollower> boundingBoxFollowerTable;
		public static float defaultScale = 0.01f;
		public static float defaultMix = 0.2f;
		public static string defaultShader = "Spine/Skeleton";
		public static bool initialized;

		const string DEFAULT_MIX_KEY = "SPINE_DEFAULT_MIX";

		static SpineEditorUtilities () {
			Initialize();
		}

		static void Initialize () {
			defaultMix = EditorPrefs.GetFloat(DEFAULT_MIX_KEY, 0.2f);

			DirectoryInfo rootDir = new DirectoryInfo(Application.dataPath);
			FileInfo[] files = rootDir.GetFiles("SpineEditorUtilities.cs", SearchOption.AllDirectories);
			editorPath = Path.GetDirectoryName(files[0].FullName.Replace("\\", "/").Replace(Application.dataPath, "Assets"));
			editorGUIPath = editorPath + "/GUI";

			Icons.Initialize();

			assetsImportedInWrongState = new HashSet<string>();
			skeletonRendererTable = new Dictionary<int, GameObject>();
			skeletonUtilityBoneTable = new Dictionary<int, SkeletonUtilityBone>();
			boundingBoxFollowerTable = new Dictionary<int, BoundingBoxFollower>();

			EditorApplication.hierarchyWindowChanged += HierarchyWindowChanged;
			EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI;

			HierarchyWindowChanged();
			initialized = true;
		}

		public static void ConfirmInitialization () {
			if (!initialized || Icons.skeleton == null)
				Initialize();
		}

		#region Hierarchy Icon
		static void HierarchyWindowChanged () {
			skeletonRendererTable.Clear();
			skeletonUtilityBoneTable.Clear();
			boundingBoxFollowerTable.Clear();

			SkeletonRenderer[] arr = Object.FindObjectsOfType<SkeletonRenderer>();
			foreach (SkeletonRenderer r in arr)
				skeletonRendererTable.Add(r.gameObject.GetInstanceID(), r.gameObject);

			SkeletonUtilityBone[] boneArr = Object.FindObjectsOfType<SkeletonUtilityBone>();
			foreach (SkeletonUtilityBone b in boneArr)
				skeletonUtilityBoneTable.Add(b.gameObject.GetInstanceID(), b);

			BoundingBoxFollower[] bbfArr = Object.FindObjectsOfType<BoundingBoxFollower>();
			foreach (BoundingBoxFollower bbf in bbfArr)
				boundingBoxFollowerTable.Add(bbf.gameObject.GetInstanceID(), bbf);
		}

		static void HierarchyWindowItemOnGUI (int instanceId, Rect selectionRect) {
			if (skeletonRendererTable.ContainsKey(instanceId)) {
				Rect r = new Rect(selectionRect);
				r.x = r.width - 15;
				r.width = 15;

				GUI.Label(r, Icons.spine);
			} else if (skeletonUtilityBoneTable.ContainsKey(instanceId)) {
				Rect r = new Rect(selectionRect);
				r.x -= 26;

				if (skeletonUtilityBoneTable[instanceId] != null) {
					if (skeletonUtilityBoneTable[instanceId].transform.childCount == 0)
						r.x += 13;

					r.y += 2;

					r.width = 13;
					r.height = 13;

					if (skeletonUtilityBoneTable[instanceId].mode == SkeletonUtilityBone.Mode.Follow) {
						GUI.DrawTexture(r, Icons.bone);
					} else {
						GUI.DrawTexture(r, Icons.poseBones);
					}
				}

			} else if (boundingBoxFollowerTable.ContainsKey(instanceId)) {
				Rect r = new Rect(selectionRect);
				r.x -= 26;

				if (boundingBoxFollowerTable[instanceId] != null) {
					if (boundingBoxFollowerTable[instanceId].transform.childCount == 0)
						r.x += 13;

					r.y += 2;

					r.width = 13;
					r.height = 13;

					GUI.DrawTexture(r, Icons.boundingBox);
				}
			}

		}
		#endregion

		static void OnPostprocessAllAssets (string[] imported, string[] deleted, string[] moved, string[] movedFromAssetPaths) {
			if (imported.Length == 0)
				return;

			// In case user used "Assets -> Reimport All", during the import process,
			// asset database is not initialized until some point. During that period,
			// all attempts to load any assets using API (i.e. AssetDatabase.LoadAssetAtPath)
			// will return null, and as result, assets won't be loaded even if they actually exists,
			// which may lead to numerous importing errors.
			// This situation also happens if Library folder is deleted from the project, which is a pretty
			// common case, since when using version control systems, the Library folder must be excluded.
			// 
			// So to avoid this, in case asset database is not available, we delay loading the assets
			// until next time.
			//
			// Unity *always* reimports some internal assets after the process is done, so this method
			// is always called once again in a state when asset database is available.
			//
			// Checking whether AssetDatabase is initialized is done by attempting to load
			// a known "marker" asset that should always be available. Failing to load this asset
			// means that AssetDatabase is not initialized.
			assetsImportedInWrongState.UnionWith(imported);
			if (AssetDatabaseAvailabilityDetector.IsAssetDatabaseAvailable()) {
				string[] combinedAssets = assetsImportedInWrongState.ToArray();
				assetsImportedInWrongState.Clear();
				ImportSpineContent(combinedAssets);
			}
		}

		public static void ImportSpineContent (string[] imported, bool reimport = false) {
			List<string> atlasPaths = new List<string>();
			List<string> imagePaths = new List<string>();
			List<string> skeletonPaths = new List<string>();

			foreach (string str in imported) {
				string extension = Path.GetExtension(str).ToLower();
				switch (extension) {
				case ".txt":
					if (str.EndsWith(".atlas.txt")) {
						atlasPaths.Add(str);
					}
					break;
				case ".png":
				case ".jpg":
					imagePaths.Add(str);
					break;
				case ".json":
					if (IsValidSpineData((TextAsset)AssetDatabase.LoadAssetAtPath(str, typeof(TextAsset))))
						skeletonPaths.Add(str);
					break;
				case ".bytes":
					if (str.ToLower().EndsWith(".skel.bytes")) {
						if (IsValidSpineData((TextAsset)AssetDatabase.LoadAssetAtPath(str, typeof(TextAsset))))
							skeletonPaths.Add(str);
					}
					break;
				}
			}


			List<AtlasAsset> atlases = new List<AtlasAsset>();

			//import atlases first
			foreach (string ap in atlasPaths) {
				if (!reimport && CheckForValidAtlas(ap))
					continue;

				TextAsset atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(ap, typeof(TextAsset));
				AtlasAsset atlas = IngestSpineAtlas(atlasText);
				atlases.Add(atlas);
			}

			//import skeletons and match them with atlases
			bool abortSkeletonImport = false;
			foreach (string sp in skeletonPaths) {
				if (!reimport && CheckForValidSkeletonData(sp)) {
					ResetExistingSkeletonData(sp);
					continue;
				}


				string dir = Path.GetDirectoryName(sp);

				var localAtlases = FindAtlasesAtPath(dir);
				var requiredPaths = GetRequiredAtlasRegions(sp);
				var atlasMatch = GetMatchingAtlas(requiredPaths, localAtlases);

				if (atlasMatch != null) {
					IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasMatch);
				} else {
					bool resolved = false;
					while (!resolved) {
						int result = EditorUtility.DisplayDialogComplex("Skeleton JSON Import Error!", "Could not find matching AtlasAsset for " + Path.GetFileNameWithoutExtension(sp), "Select", "Skip", "Abort");
						switch (result) {
						case -1:
							Debug.Log("Select Atlas");
							AtlasAsset selectedAtlas = GetAtlasDialog(Path.GetDirectoryName(sp));
							if (selectedAtlas != null) {
								localAtlases.Clear();
								localAtlases.Add(selectedAtlas);
								atlasMatch = GetMatchingAtlas(requiredPaths, localAtlases);
								if (atlasMatch != null) {
									resolved = true;
									IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasMatch);
								}
							}

							break;
						case 0:
							var atlasList = MultiAtlasDialog(requiredPaths, Path.GetDirectoryName(sp), Path.GetFileNameWithoutExtension(sp));

							if (atlasList != null)
								IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasList.ToArray());

							resolved = true;
							break;

						case 1:
							Debug.Log("Skipped importing: " + Path.GetFileName(sp));
							resolved = true;
							break;


						case 2:
							//abort
							abortSkeletonImport = true;
							resolved = true;
							break;
						}
					}
				}

				if (abortSkeletonImport)
					break;
			}

			//TODO:  any post processing of images
		}

		static bool CheckForValidSkeletonData (string skeletonJSONPath) {

			string dir = Path.GetDirectoryName(skeletonJSONPath);
			TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset));
			DirectoryInfo dirInfo = new DirectoryInfo(dir);

			FileInfo[] files = dirInfo.GetFiles("*.asset");

			foreach (var f in files) {
				string localPath = dir + "/" + f.Name;
				var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
				if (obj is SkeletonDataAsset) {
					var skeletonDataAsset = (SkeletonDataAsset)obj;
					if (skeletonDataAsset.skeletonJSON == textAsset)
						return true;
				}
			}

			return false;
		}

		static void ResetExistingSkeletonData (string skeletonJSONPath) {

			string dir = Path.GetDirectoryName(skeletonJSONPath);
			TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset));
			DirectoryInfo dirInfo = new DirectoryInfo(dir);

			FileInfo[] files = dirInfo.GetFiles("*.asset");

			foreach (var f in files) {
				string localPath = dir + "/" + f.Name;
				var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
				if (obj is SkeletonDataAsset) {
					var skeletonDataAsset = (SkeletonDataAsset)obj;

					if (skeletonDataAsset.skeletonJSON == textAsset) {
						if (Selection.activeObject == skeletonDataAsset)
							Selection.activeObject = null;

						skeletonDataAsset.Reset();

						string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(skeletonDataAsset));
						string lastHash = EditorPrefs.GetString(guid + "_hash");

						// For some weird reason sometimes Unity loses the internal Object pointer,
						// and as a result, all comparisons with null returns true.
						// But the C# wrapper is still alive, so we can "restore" the object
						// by reloading it from its Instance ID.
						AtlasAsset[] skeletonDataAtlasAssets = skeletonDataAsset.atlasAssets;
						if (skeletonDataAtlasAssets != null) {
							for (int i = 0; i < skeletonDataAtlasAssets.Length; i++) {
								if (!ReferenceEquals(null, skeletonDataAtlasAssets[i]) &&
									skeletonDataAtlasAssets[i].Equals(null) &&
									skeletonDataAtlasAssets[i].GetInstanceID() != 0
								) {
									skeletonDataAtlasAssets[i] = EditorUtility.InstanceIDToObject(skeletonDataAtlasAssets[i].GetInstanceID()) as AtlasAsset;
								}
							}
						}

						SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
						string currentHash = skeletonData != null ? skeletonData.Hash : null;
						if (currentHash == null || lastHash != currentHash) {
							//do any upkeep on synchronized assets
							UpdateMecanimClips(skeletonDataAsset);
						}

						if (currentHash != null) {
							EditorPrefs.SetString(guid + "_hash", currentHash);
						}
					}
				}
			}
		}

		static bool CheckForValidAtlas (string atlasPath) {
			return false;
			//////////////DEPRECATED - always check for new atlas data now
			/*
		string dir = Path.GetDirectoryName(atlasPath);
		TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(TextAsset));
		DirectoryInfo dirInfo = new DirectoryInfo(dir);

		FileInfo[] files = dirInfo.GetFiles("*.asset");

		foreach (var f in files) {
			string localPath = dir + "/" + f.Name;
			var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
			if (obj is AtlasAsset) {
				var atlasAsset = (AtlasAsset)obj;
				if (atlasAsset.atlasFile == textAsset) {

					
					Atlas atlas = atlasAsset.GetAtlas();
					FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
					List<AtlasRegion> regions = (List<AtlasRegion>)field.GetValue(atlas);
					string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
					string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
					string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);

					for (int i = 0; i < regions.Count; i++) {
						AtlasRegion region = regions[i];
						string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/");
						GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject));

						if (prefab != null) {
							Debug.Log("Updating: " + region.name);
							BakeRegion(atlasAsset, region);
						}
					}
					

					return true;
				}
					
			}
		}

		return false;
	
		*/
		}

		static List<AtlasAsset> MultiAtlasDialog (List<string> requiredPaths, string initialDirectory, string header = "") {

			List<AtlasAsset> atlasAssets = new List<AtlasAsset>();

			bool resolved = false;
			string lastAtlasPath = initialDirectory;
			while (!resolved) {
				StringBuilder sb = new StringBuilder();
				sb.AppendLine(header);
				sb.AppendLine("Atlases:");
				if (atlasAssets.Count == 0) {
					sb.AppendLine("\t--none--");
				}
				for (int i = 0; i < atlasAssets.Count; i++) {
					sb.AppendLine("\t" + atlasAssets[i].name);
				}

				sb.AppendLine();
				sb.AppendLine("Missing Regions:");

				List<string> missingRegions = new List<string>(requiredPaths);

				foreach (var atlasAsset in atlasAssets) {
					var atlas = atlasAsset.GetAtlas();
					for (int i = 0; i < missingRegions.Count; i++) {
						if (atlas.FindRegion(missingRegions[i]) != null) {
							missingRegions.RemoveAt(i);
							i--;
						}
					}
				}

				if (missingRegions.Count == 0) {
					break;
				}

				for (int i = 0; i < missingRegions.Count; i++) {
					sb.AppendLine("\t" + missingRegions[i]);
				}

				int result = EditorUtility.DisplayDialogComplex("Atlas Selection", sb.ToString(), "Select", "Finish", "Abort");

				switch (result) {
				case 0:
					AtlasAsset selectedAtlasAsset = GetAtlasDialog(lastAtlasPath);
					if (selectedAtlasAsset != null) {
						var atlas = selectedAtlasAsset.GetAtlas();
						bool hasValidRegion = false;
						foreach (string str in missingRegions) {
							if (atlas.FindRegion(str) != null) {
								hasValidRegion = true;
								break;
							}
						}

						atlasAssets.Add(selectedAtlasAsset);
					}
					break;

				case 1:
					resolved = true;
					break;

				case 2:
					atlasAssets = null;
					resolved = true;
					break;
				}


			}


			return atlasAssets;
		}

		static AtlasAsset GetAtlasDialog (string dirPath) {
			string path = EditorUtility.OpenFilePanel("Select AtlasAsset...", dirPath, "asset");
			if (path == "")
				return null;

			int subLen = Application.dataPath.Length - 6;
			string assetRelativePath = path.Substring(subLen, path.Length - subLen).Replace("\\", "/");

			Object obj = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(AtlasAsset));

			if (obj == null || obj.GetType() != typeof(AtlasAsset))
				return null;

			return (AtlasAsset)obj;
		}

		static void AddRequiredAtlasRegionsFromBinary (string skeletonDataPath, List<string> requiredPaths) {
			SkeletonBinary binary = new SkeletonBinary(new AtlasRequirementLoader(requiredPaths));
			TextAsset data = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonDataPath, typeof(TextAsset));
			MemoryStream input = new MemoryStream(data.bytes);
			binary.ReadSkeletonData(input);
			binary = null;
		}

		public static List<string> GetRequiredAtlasRegions (string skeletonDataPath) {
			List<string> requiredPaths = new List<string>();

			if (skeletonDataPath.Contains(".skel")) {
				AddRequiredAtlasRegionsFromBinary(skeletonDataPath, requiredPaths);
				return requiredPaths;
			}

			TextAsset spineJson = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonDataPath, typeof(TextAsset));

			StringReader reader = new StringReader(spineJson.text);
			var root = Json.Deserialize(reader) as Dictionary<string, object>;

			foreach (KeyValuePair<string, object> entry in (Dictionary<string, object>)root["skins"]) {
				foreach (KeyValuePair<string, object> slotEntry in (Dictionary<string, object>)entry.Value) {

					foreach (KeyValuePair<string, object> attachmentEntry in ((Dictionary<string, object>)slotEntry.Value)) {
						var data = ((Dictionary<string, object>)attachmentEntry.Value);
						if (data.ContainsKey("type")) {
							if ((string)data["type"] == "boundingbox") {
								continue;
							}

						}
						if (data.ContainsKey("path"))
							requiredPaths.Add((string)data["path"]);
						else if (data.ContainsKey("name"))
							requiredPaths.Add((string)data["name"]);
						else
							requiredPaths.Add(attachmentEntry.Key);
						//requiredPaths.Add((string)sdf["path"]);
					}
				}
			}

			return requiredPaths;
		}
		static AtlasAsset GetMatchingAtlas (List<string> requiredPaths, List<AtlasAsset> atlasAssets) {
			AtlasAsset atlasAssetMatch = null;

			foreach (AtlasAsset a in atlasAssets) {
				Atlas atlas = a.GetAtlas();
				bool failed = false;
				foreach (string regionPath in requiredPaths) {
					if (atlas.FindRegion(regionPath) == null) {
						failed = true;
						break;
					}
				}

				if (!failed) {
					atlasAssetMatch = a;
					break;
				}

			}

			return atlasAssetMatch;
		}

		static List<AtlasAsset> FindAtlasesAtPath (string path) {
			List<AtlasAsset> arr = new List<AtlasAsset>();

			DirectoryInfo dir = new DirectoryInfo(path);
			FileInfo[] assetInfoArr = dir.GetFiles("*.asset");

			int subLen = Application.dataPath.Length - 6;

			foreach (var f in assetInfoArr) {
				string assetRelativePath = f.FullName.Substring(subLen, f.FullName.Length - subLen).Replace("\\", "/");

				Object obj = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(AtlasAsset));
				if (obj != null) {
					arr.Add(obj as AtlasAsset);
				}

			}


			return arr;
		}

		public static bool IsValidSpineData (TextAsset asset) {
			if (asset.name.Contains(".skel")) return true;

			object obj = null;
			try {
				obj = Json.Deserialize(new StringReader(asset.text));
			} catch (System.Exception) {
			}
			if (obj == null) {
				Debug.LogError("Is not valid JSON");
				return false;
			}

			Dictionary<string, object> root = (Dictionary<string, object>)obj;

			if (!root.ContainsKey("skeleton"))
				return false;

			Dictionary<string, object> skeletonInfo = (Dictionary<string, object>)root["skeleton"];

			string spineVersion = (string)skeletonInfo["spine"];
			//TODO:  reject old versions

			return true;
		}

		static AtlasAsset IngestSpineAtlas (TextAsset atlasText) {
			if (atlasText == null) {
				Debug.LogWarning("Atlas source cannot be null!");
				return null;
			}

			string primaryName = Path.GetFileNameWithoutExtension(atlasText.name).Replace(".atlas", "");
			string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(atlasText));

			string atlasPath = assetPath + "/" + primaryName + "_Atlas.asset";

			AtlasAsset atlasAsset = (AtlasAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAsset));

			List<Material> vestigialMaterials = new List<Material>();

			if (atlasAsset == null)
				atlasAsset = AtlasAsset.CreateInstance<AtlasAsset>();
			else {
				foreach (Material m in atlasAsset.materials)
					vestigialMaterials.Add(m);
			}

			atlasAsset.atlasFile = atlasText;

			//strip CR
			string atlasStr = atlasText.text;
			atlasStr = atlasStr.Replace("\r", "");

			string[] atlasLines = atlasStr.Split('\n');
			List<string> pageFiles = new List<string>();
			for (int i = 0; i < atlasLines.Length - 1; i++) {
				if (atlasLines[i].Trim().Length == 0)
					pageFiles.Add(atlasLines[i + 1].Trim());
			}

			atlasAsset.materials = new Material[pageFiles.Count];

			for (int i = 0; i < pageFiles.Count; i++) {
				string texturePath = assetPath + "/" + pageFiles[i];
				Texture2D texture = (Texture2D)AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D));

				TextureImporter texImporter = (TextureImporter)TextureImporter.GetAtPath(texturePath);
				texImporter.textureType = TextureImporterType.Advanced;
				texImporter.textureFormat = TextureImporterFormat.AutomaticTruecolor;
				texImporter.mipmapEnabled = false;
				texImporter.alphaIsTransparency = false;
				texImporter.maxTextureSize = 2048;

				EditorUtility.SetDirty(texImporter);
				AssetDatabase.ImportAsset(texturePath);
				AssetDatabase.SaveAssets();

				string pageName = Path.GetFileNameWithoutExtension(pageFiles[i]);

				//because this looks silly
				if (pageName == primaryName && pageFiles.Count == 1)
					pageName = "Material";

				string materialPath = assetPath + "/" + primaryName + "_" + pageName + ".mat";
				Material mat = (Material)AssetDatabase.LoadAssetAtPath(materialPath, typeof(Material));

				if (mat == null) {
					mat = new Material(Shader.Find(defaultShader));
					AssetDatabase.CreateAsset(mat, materialPath);
				} else {
					vestigialMaterials.Remove(mat);
				}

				mat.mainTexture = texture;
				EditorUtility.SetDirty(mat);

				AssetDatabase.SaveAssets();

				atlasAsset.materials[i] = mat;
			}

			for (int i = 0; i < vestigialMaterials.Count; i++)
				AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(vestigialMaterials[i]));

			if (AssetDatabase.GetAssetPath(atlasAsset) == "")
				AssetDatabase.CreateAsset(atlasAsset, atlasPath);
			else
				atlasAsset.Reset();

			EditorUtility.SetDirty(atlasAsset);

			AssetDatabase.SaveAssets();


			//iterate regions and bake marked
			Atlas atlas = atlasAsset.GetAtlas();
			FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
			List<AtlasRegion> regions = (List<AtlasRegion>)field.GetValue(atlas);
			string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
			string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
			string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);

			bool hasBakedRegions = false;
			for (int i = 0; i < regions.Count; i++) {
				AtlasRegion region = regions[i];
				string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/");
				GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject));

				if (prefab != null) {
					BakeRegion(atlasAsset, region, false);
					hasBakedRegions = true;
				}
			}

			if (hasBakedRegions) {
				AssetDatabase.SaveAssets();
				AssetDatabase.Refresh();
			}

			return (AtlasAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAsset));
		}

		public static GameObject BakeRegion (AtlasAsset atlasAsset, AtlasRegion region, bool autoSave = true) {
			Atlas atlas = atlasAsset.GetAtlas();
			string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
			string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
			string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
			string bakedPrefabPath = Path.Combine(bakedDirPath, GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/");

			GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject));
			GameObject root;
			Mesh mesh;
			bool isNewPrefab = false;

			if (!Directory.Exists(bakedDirPath))
				Directory.CreateDirectory(bakedDirPath);

			if (prefab == null) {
				root = new GameObject("temp", typeof(MeshFilter), typeof(MeshRenderer));
				prefab = (GameObject)PrefabUtility.CreatePrefab(bakedPrefabPath, root);
				isNewPrefab = true;
				Object.DestroyImmediate(root);
			}

			mesh = (Mesh)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(Mesh));

			Material mat = null;
			mesh = atlasAsset.GenerateMesh(region.name, mesh, out mat);
			if (isNewPrefab) {
				AssetDatabase.AddObjectToAsset(mesh, prefab);
				prefab.GetComponent<MeshFilter>().sharedMesh = mesh;
			}

			EditorUtility.SetDirty(mesh);
			EditorUtility.SetDirty(prefab);

			if (autoSave) {
				AssetDatabase.SaveAssets();
				AssetDatabase.Refresh();
			}


			prefab.GetComponent<MeshRenderer>().sharedMaterial = mat;

			return prefab;
		}

		public static string GetPathSafeRegionName (AtlasRegion region) {
			return region.name.Replace("/", "_");
		}

		static SkeletonDataAsset IngestSpineProject (TextAsset spineJson, params AtlasAsset[] atlasAssets) {
			string primaryName = Path.GetFileNameWithoutExtension(spineJson.name);
			string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
			string filePath = assetPath + "/" + primaryName + "_SkeletonData.asset";

			if (spineJson != null && atlasAssets != null) {

				SkeletonDataAsset skelDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset));
				if (skelDataAsset == null) {
					skelDataAsset = SkeletonDataAsset.CreateInstance<SkeletonDataAsset>();
					skelDataAsset.atlasAssets = atlasAssets;
					skelDataAsset.skeletonJSON = spineJson;
					skelDataAsset.fromAnimation = new string[0];
					skelDataAsset.toAnimation = new string[0];
					skelDataAsset.duration = new float[0];
					skelDataAsset.defaultMix = defaultMix;
					skelDataAsset.scale = defaultScale;

					AssetDatabase.CreateAsset(skelDataAsset, filePath);
					AssetDatabase.SaveAssets();
				} else {
					skelDataAsset.atlasAssets = atlasAssets;
					skelDataAsset.Reset();
					skelDataAsset.GetSkeletonData(true);
				}

				return skelDataAsset;
			} else {
				EditorUtility.DisplayDialog("Error!", "Must specify both Spine JSON and AtlasAsset array", "OK");
				return null;
			}
		}

		#region SkeletonAnimation Menu
		[MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)", false, 10)]
		static void InstantiateSkeletonAnimation () {
			Object[] arr = Selection.objects;
			foreach (Object o in arr) {
				string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
				string skinName = EditorPrefs.GetString(guid + "_lastSkin", "");

				InstantiateSkeletonAnimation((SkeletonDataAsset)o, skinName);
				SceneView.RepaintAll();
			}
		}

		[MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)", true, 10)]
		static bool ValidateInstantiateSkeletonAnimation () {
			Object[] arr = Selection.objects;

			if (arr.Length == 0)
				return false;

			foreach (Object o in arr) {
				if (o.GetType() != typeof(SkeletonDataAsset))
					return false;
			}

			return true;
		}

		public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, string skinName) {
			return InstantiateSkeletonAnimation(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
		}

		public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
			string spineGameObjectName = string.Format("Spine GameObject ({0})", skeletonDataAsset.name.Replace("_SkeletonData", ""));
			GameObject go = new GameObject(spineGameObjectName, typeof(MeshFilter), typeof(MeshRenderer), typeof(SkeletonAnimation));
			SkeletonAnimation anim = go.GetComponent<SkeletonAnimation>();
			anim.skeletonDataAsset = skeletonDataAsset;

			bool requiresNormals = false;

			foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) {
				foreach (Material m in atlasAsset.materials) {
					if (m.shader.name.Contains("Lit")) {
						requiresNormals = true;
						break;
					}
				}
			}



			anim.calculateNormals = requiresNormals;

			SkeletonData data = skeletonDataAsset.GetSkeletonData(true);

			if (data == null) {
				for (int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++) {
					string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
					skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
				}

				data = skeletonDataAsset.GetSkeletonData(true);
			}

			if (skin == null)
				skin = data.DefaultSkin;

			if (skin == null)
				skin = data.Skins.Items[0];

			anim.Initialize(false);

			anim.skeleton.SetSkin(skin);
			anim.initialSkinName = skin.Name;

			anim.skeleton.Update(1);
			anim.state.Update(1);
			anim.state.Apply(anim.skeleton);
			anim.skeleton.UpdateWorldTransform();

			return anim;
		}
		#endregion

		#region SkeletonAnimator
		#if SPINE_SKELETONANIMATOR
		static void UpdateMecanimClips (SkeletonDataAsset skeletonDataAsset) {
			if (skeletonDataAsset.controller == null)
				return;

			SkeletonBaker.GenerateMecanimAnimationClips(skeletonDataAsset);
		}

		[MenuItem("Assets/Spine/Instantiate (Mecanim)", false, 100)]
		static void InstantiateSkeletonAnimator () {
			Object[] arr = Selection.objects;
			foreach (Object o in arr) {
				string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
				string skinName = EditorPrefs.GetString(guid + "_lastSkin", "");

				InstantiateSkeletonAnimator((SkeletonDataAsset)o, skinName);
				SceneView.RepaintAll();
			}
		}

		[MenuItem("Assets/Spine/Instantiate (Mecanim)", true, 100)]
		static bool ValidateInstantiateSkeletonAnimator () {
			Object[] arr = Selection.objects;

			if (arr.Length == 0)
				return false;

			foreach (Object o in arr) {
				if (o.GetType() != typeof(SkeletonDataAsset))
					return false;
			}

			return true;
		}

		public static SkeletonAnimator InstantiateSkeletonAnimator (SkeletonDataAsset skeletonDataAsset, string skinName) {
			return InstantiateSkeletonAnimator(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
		}

		public static SkeletonAnimator InstantiateSkeletonAnimator (SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
			string spineGameObjectName = string.Format("Spine Mecanim GameObject ({0})", skeletonDataAsset.name.Replace("_SkeletonData", ""));
			GameObject go = new GameObject(spineGameObjectName, typeof(MeshFilter), typeof(MeshRenderer), typeof(Animator), typeof(SkeletonAnimator));

			if (skeletonDataAsset.controller == null) {
				SkeletonBaker.GenerateMecanimAnimationClips(skeletonDataAsset);
			}

			go.GetComponent<Animator>().runtimeAnimatorController = skeletonDataAsset.controller;

			SkeletonAnimator anim = go.GetComponent<SkeletonAnimator>();
			anim.skeletonDataAsset = skeletonDataAsset;

			bool requiresNormals = false;

			foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) {
				foreach (Material m in atlasAsset.materials) {
					if (m.shader.name.Contains("Lit")) {
						requiresNormals = true;
						break;
					}
				}
			}

			anim.calculateNormals = requiresNormals;

			SkeletonData data = skeletonDataAsset.GetSkeletonData(true);

			if (data == null) {
				for (int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++) {
					string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
					skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
				}

				data = skeletonDataAsset.GetSkeletonData(true);
			}

			if (skin == null)
				skin = data.DefaultSkin;

			if (skin == null)
				skin = data.Skins.Items[0];

			anim.Initialize(false);

			anim.skeleton.SetSkin(skin);
			anim.initialSkinName = skin.Name;

			anim.skeleton.Update(1);
			anim.skeleton.UpdateWorldTransform();
			anim.LateUpdate();

			return anim;
		}
		#endif
		#endregion

		#region Spine Preferences
		static bool preferencesLoaded = false;

		[PreferenceItem("Spine")]
		static void PreferencesGUI () {
			if (!preferencesLoaded) {
				preferencesLoaded = true;
				defaultMix = EditorPrefs.GetFloat(DEFAULT_MIX_KEY, 0.2f);
			}


			EditorGUILayout.LabelField("Auto-Import Settings", EditorStyles.boldLabel);
			EditorGUI.BeginChangeCheck();
			defaultMix = EditorGUILayout.FloatField("Default Mix", defaultMix);
			if (EditorGUI.EndChangeCheck())
				EditorPrefs.SetFloat(DEFAULT_MIX_KEY, defaultMix);

			GUILayout.Space(20);
			EditorGUILayout.LabelField("3rd Party Settings", EditorStyles.boldLabel);
			GUILayout.BeginHorizontal();
			EditorGUILayout.PrefixLabel("TK2D");

			if (GUILayout.Button("Enable", GUILayout.Width(64)))
				EnableTK2D();
			if (GUILayout.Button("Disable", GUILayout.Width(64)))
				DisableTK2D();
			GUILayout.EndHorizontal();
		}
		#endregion

		//TK2D Support
		const string SPINE_TK2D_DEFINE = "SPINE_TK2D";

		static void EnableTK2D () {
			bool added = false;
			foreach (BuildTargetGroup group in System.Enum.GetValues(typeof(BuildTargetGroup))) {
				if (group == BuildTargetGroup.Unknown)
					continue;

				string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
				if (!defines.Contains(SPINE_TK2D_DEFINE)) {
					added = true;
					if (defines.EndsWith(";", System.StringComparison.Ordinal))
						defines = defines + SPINE_TK2D_DEFINE;
					else
						defines = defines + ";" + SPINE_TK2D_DEFINE;

					PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines);
				}
			}

			if (added) {
				Debug.LogWarning("Setting Scripting Define Symbol " + SPINE_TK2D_DEFINE);
			} else {
				Debug.LogWarning("Already Set Scripting Define Symbol " + SPINE_TK2D_DEFINE);
			}
		}


		static void DisableTK2D () {
			bool removed = false;
			foreach (BuildTargetGroup group in System.Enum.GetValues(typeof(BuildTargetGroup))) {
				string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
				if (defines.Contains(SPINE_TK2D_DEFINE)) {
					removed = true;
					if (defines.Contains(SPINE_TK2D_DEFINE + ";"))
						defines = defines.Replace(SPINE_TK2D_DEFINE + ";", "");
					else
						defines = defines.Replace(SPINE_TK2D_DEFINE, "");

					PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines);
				}
			}

			if (removed) {
				Debug.LogWarning("Removing Scripting Define Symbol " + SPINE_TK2D_DEFINE);
			} else {
				Debug.LogWarning("Already Removed Scripting Define Symbol " + SPINE_TK2D_DEFINE);
			}
		}

		public class AtlasRequirementLoader : AttachmentLoader {

			List<string> requirementList;
			public AtlasRequirementLoader (List<string> requirementList) {
				this.requirementList = requirementList;
			}

			public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) {
				requirementList.Add(path);
				return new RegionAttachment(name);
			}

			public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) {
				requirementList.Add(path);
				return new MeshAttachment(name);
			}

			public WeightedMeshAttachment NewWeightedMeshAttachment(Skin skin, string name, string path) {
				requirementList.Add(path);
				return new WeightedMeshAttachment(name);
			}

			public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) {
				return new BoundingBoxAttachment(name);
			}
		}
	}

}

