using UnityEngine;
using UnityEngine.TextCore;
using UnityEngine.U2D;
using UnityEditor;
using System.Linq;
using System.IO;
using System.Collections;
using System.Collections.Generic;
namespace TMPro.EditorUtilities
{
    public static class TMP_SpriteAssetMenu
    {
        // Add a Context Menu to the Sprite Asset Editor Panel to Create and Add a Default Material.
        [MenuItem("CONTEXT/TMP_SpriteAsset/Add Default Material", true, 2200)]
        static bool AddDefaultMaterialValidate(MenuCommand command)
        {
            return AssetDatabase.IsOpenForEdit(command.context);
        }
        [MenuItem("CONTEXT/TMP_SpriteAsset/Add Default Material", false, 2200)]
        static void AddDefaultMaterial(MenuCommand command)
        {
            TMP_SpriteAsset spriteAsset = (TMP_SpriteAsset)command.context;
            // Make sure the sprite asset already contains a default material
            if (spriteAsset != null && spriteAsset.material == null)
            {
                // Add new default material for sprite asset.
                AddDefaultMaterial(spriteAsset);
            }
        }
        // Add a Context Menu to the Sprite Asset Editor Panel to update existing sprite assets.
        [MenuItem("CONTEXT/TMP_SpriteAsset/Update Sprite Asset", true, 2100)]
        static bool UpdateSpriteAssetValidate(MenuCommand command)
        {
            return AssetDatabase.IsOpenForEdit(command.context);
        }
        [MenuItem("CONTEXT/TMP_SpriteAsset/Update Sprite Asset", false, 2100)]
        static void UpdateSpriteAsset(MenuCommand command)
        {
            TMP_SpriteAsset spriteAsset = (TMP_SpriteAsset)command.context;
            if (spriteAsset == null)
                return;
            UpdateSpriteAsset(spriteAsset);
        }
        internal static void UpdateSpriteAsset(TMP_SpriteAsset spriteAsset)
        {
            // Get a list of all the sprites contained in the texture referenced by the sprite asset.
            // This only works if the texture is set to sprite mode.
            string filePath = AssetDatabase.GetAssetPath(spriteAsset.spriteSheet);
            if (string.IsNullOrEmpty(filePath))
                return;
            // Get all the sprites defined in the sprite sheet texture referenced by this sprite asset.
            Sprite[] sprites = AssetDatabase.LoadAllAssetsAtPath(filePath).Select(x => x as Sprite).Where(x => x != null).ToArray();
            // Return if sprite sheet texture does not have any sprites defined in it.
            if (sprites.Length == 0)
            {
                Debug.Log("Sprite Asset [" + spriteAsset.name + "]'s atlas texture does not appear to have any sprites defined in it. Use the Unity Sprite Editor to define sprites for this texture.", spriteAsset.spriteSheet);
                return;
            }
            List spriteGlyphTable = spriteAsset.spriteGlyphTable;
            // Find available glpyh indexes
            uint[] existingGlyphIndexes = spriteGlyphTable.Select(x => x.index).ToArray();
            List availableGlyphIndexes = new List();
            uint lastGlyphIndex = existingGlyphIndexes.Length > 0 ? existingGlyphIndexes.Last() : 0;
            int elementIndex = 0;
            for (uint i = 0; i < lastGlyphIndex; i++)
            {
                uint existingGlyphIndex = existingGlyphIndexes[elementIndex];
                if (i == existingGlyphIndex)
                    elementIndex += 1;
                else
                    availableGlyphIndexes.Add(i);
            }
            // Iterate over sprites contained in the updated sprite sheet to identify new and / or modified sprites.
            for (int i = 0; i < sprites.Length; i++)
            {
                Sprite sprite = sprites[i];
                // Check if current sprites is already contained in the sprite glyph table of the sprite asset.
                TMP_SpriteGlyph spriteGlyph = spriteGlyphTable.FirstOrDefault(x => x.sprite == sprite);
                if (spriteGlyph != null)
                {
                    // update existing sprite glyph
                    if (spriteGlyph.glyphRect.x != sprite.rect.x || spriteGlyph.glyphRect.y != sprite.rect.y || spriteGlyph.glyphRect.width != sprite.rect.width || spriteGlyph.glyphRect.height != sprite.rect.height)
                        spriteGlyph.glyphRect = new GlyphRect(sprite.rect);
                }
                else
                {
                    TMP_SpriteCharacter spriteCharacter;
                    // Check if this sprite potentially exists under the same name in the sprite character table.
                    if (spriteAsset.spriteCharacterTable != null && spriteAsset.spriteCharacterTable.Count > 0)
                    {
                        spriteCharacter = spriteAsset.spriteCharacterTable.FirstOrDefault(x => x.name == sprite.name);
                        spriteGlyph = spriteCharacter != null ? spriteGlyphTable[(int)spriteCharacter.glyphIndex] : null;
                        if (spriteGlyph != null)
                        {
                            // Update sprite reference and data
                            spriteGlyph.sprite = sprite;
                            if (spriteGlyph.glyphRect.x != sprite.rect.x || spriteGlyph.glyphRect.y != sprite.rect.y || spriteGlyph.glyphRect.width != sprite.rect.width || spriteGlyph.glyphRect.height != sprite.rect.height)
                                spriteGlyph.glyphRect = new GlyphRect(sprite.rect);
                        }
                    }
                    // Add new Sprite Glyph to the table
                    spriteGlyph = new TMP_SpriteGlyph();
                    // Get available glyph index
                    if (availableGlyphIndexes.Count > 0)
                    {
                        spriteGlyph.index = availableGlyphIndexes[0];
                        availableGlyphIndexes.RemoveAt(0);
                    }
                    else
                        spriteGlyph.index = (uint)spriteGlyphTable.Count;
                    spriteGlyph.metrics = new GlyphMetrics(sprite.rect.width, sprite.rect.height, -sprite.pivot.x, sprite.rect.height - sprite.pivot.y, sprite.rect.width);
                    spriteGlyph.glyphRect = new GlyphRect(sprite.rect);
                    spriteGlyph.scale = 1.0f;
                    spriteGlyph.sprite = sprite;
                    spriteGlyphTable.Add(spriteGlyph);
                    spriteCharacter = new TMP_SpriteCharacter(0xFFFE, spriteGlyph);
					// Special handling for .notdef sprite name.
                    string fileNameToLowerInvariant = sprite.name.ToLowerInvariant();
                    if (fileNameToLowerInvariant == ".notdef" || fileNameToLowerInvariant == "notdef")
                    {
                        spriteCharacter.name = fileNameToLowerInvariant;
                        spriteCharacter.unicode = 0;
                    }
                    else
                    {
                        spriteCharacter.unicode = 0xFFFE;
                        if (!string.IsNullOrEmpty(sprite.name) && sprite.name.Length > 2 && sprite.name[0] == '0' && (sprite.name[1] == 'x' || sprite.name[1] == 'X'))
                        {
                            spriteCharacter.unicode = (uint)TMP_TextUtilities.StringHexToInt(sprite.name.Remove(0, 2));
                        }
                        spriteCharacter.name = sprite.name;
                    }
                    spriteCharacter.scale = 1.0f;
                    spriteAsset.spriteCharacterTable.Add(spriteCharacter);
                }
            }
            // Update Sprite Character Table to replace unicode 0x0 by 0xFFFE
            for (int i = 0; i < spriteAsset.spriteCharacterTable.Count; i++)
            {
                TMP_SpriteCharacter spriteCharacter = spriteAsset.spriteCharacterTable[i];
                if (spriteCharacter.unicode == 0)
                    spriteCharacter.unicode = 0xFFFE;
            }
            // Sort glyph table by glyph index
            spriteAsset.SortGlyphTable();
            spriteAsset.UpdateLookupTables();
            TMPro_EventManager.ON_SPRITE_ASSET_PROPERTY_CHANGED(true, spriteAsset);
            EditorUtility.SetDirty(spriteAsset);
        }
        [MenuItem("Assets/Create/TextMeshPro/Sprite Asset", false, 200)]
        static void CreateSpriteAsset()
        {
            Object[] targets = Selection.objects;
            if (targets == null)
            {
                Debug.LogWarning("A Sprite Texture must first be selected in order to create a Sprite Asset.");
                return;
            }
            // Make sure TMP Essential Resources have been imported in the user project.
            if (TMP_Settings.instance == null)
            {
                Debug.Log("Unable to create sprite asset. Please import the TMP Essential Resources.");
                // Show Window to Import TMP Essential Resources
                return;
            }
            for (int i = 0; i < targets.Length; i++)
            {
                Object target = targets[i];
                // Make sure the selection is a font file
                if (target == null || target.GetType() != typeof(Texture2D))
                {
                    Debug.LogWarning("Selected Object [" + target.name + "] is not a Sprite Texture. A Sprite Texture must be selected in order to create a Sprite Asset.", target);
                    continue;
                }
                CreateSpriteAssetFromSelectedObject(target);
            }
        }
        static void CreateSpriteAssetFromSelectedObject(Object target)
        {
            // Get the path to the selected asset.
            string filePathWithName = AssetDatabase.GetAssetPath(target);
            string fileNameWithExtension = Path.GetFileName(filePathWithName);
            string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePathWithName);
            string filePath = filePathWithName.Replace(fileNameWithExtension, "");
            string uniquePath = AssetDatabase.GenerateUniqueAssetPath(filePath + fileNameWithoutExtension + ".asset");
            // Create new Sprite Asset
            TMP_SpriteAsset spriteAsset = ScriptableObject.CreateInstance();
            AssetDatabase.CreateAsset(spriteAsset, uniquePath);
            spriteAsset.version = "1.1.0";
            // Compute the hash code for the sprite asset.
            spriteAsset.hashCode = TMP_TextUtilities.GetSimpleHashCode(spriteAsset.name);
            List spriteGlyphTable = new List();
            List spriteCharacterTable = new List();
            if (target.GetType() == typeof(Texture2D))
            {
                Texture2D sourceTex = target as Texture2D;
                // Assign new Sprite Sheet texture to the Sprite Asset.
                spriteAsset.spriteSheet = sourceTex;
                PopulateSpriteTables(sourceTex, ref spriteCharacterTable, ref spriteGlyphTable);
                spriteAsset.spriteCharacterTable = spriteCharacterTable;
                spriteAsset.spriteGlyphTable = spriteGlyphTable;
                // Add new default material for sprite asset.
                AddDefaultMaterial(spriteAsset);
            }
            else if (target.GetType() == typeof(SpriteAtlas))
            {
                //SpriteAtlas spriteAtlas = target as SpriteAtlas;
                //PopulateSpriteTables(spriteAtlas, ref spriteCharacterTable, ref spriteGlyphTable);
                //spriteAsset.spriteCharacterTable = spriteCharacterTable;
                //spriteAsset.spriteGlyphTable = spriteGlyphTable;
                //spriteAsset.spriteSheet = spriteGlyphTable[0].sprite.texture;
                //// Add new default material for sprite asset.
                //AddDefaultMaterial(spriteAsset);
            }
            // Update Lookup tables.
            spriteAsset.UpdateLookupTables();
            // Get the Sprites contained in the Sprite Sheet
            EditorUtility.SetDirty(spriteAsset);
            //spriteAsset.sprites = sprites;
            // Set source texture back to Not Readable.
            //texImporter.isReadable = false;
            AssetDatabase.SaveAssets();
            AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(spriteAsset));  // Re-import font asset to get the new updated version.
            //AssetDatabase.Refresh();
        }
        static void PopulateSpriteTables(Texture source, ref List spriteCharacterTable, ref List spriteGlyphTable)
        {
            //Debug.Log("Creating new Sprite Asset.");
            string filePath = AssetDatabase.GetAssetPath(source);
            // Get all the Sprites sorted by Index
            Sprite[] sprites = AssetDatabase.LoadAllAssetsAtPath(filePath).Select(x => x as Sprite).Where(x => x != null).OrderByDescending(x => x.rect.y).ThenBy(x => x.rect.x).ToArray();
            for (int i = 0; i < sprites.Length; i++)
            {
                Sprite sprite = sprites[i];
                TMP_SpriteGlyph spriteGlyph = new TMP_SpriteGlyph();
                spriteGlyph.index = (uint)i;
                spriteGlyph.metrics = new GlyphMetrics(sprite.rect.width, sprite.rect.height, -sprite.pivot.x, sprite.rect.height - sprite.pivot.y, sprite.rect.width);
                spriteGlyph.glyphRect = new GlyphRect(sprite.rect);
                spriteGlyph.scale = 1.0f;
                spriteGlyph.sprite = sprite;
                spriteGlyphTable.Add(spriteGlyph);
                TMP_SpriteCharacter spriteCharacter = new TMP_SpriteCharacter(0xFFFE, spriteGlyph);
                // Special handling for .notdef sprite name.
                string fileNameToLowerInvariant = sprite.name.ToLowerInvariant();
                if (fileNameToLowerInvariant == ".notdef" || fileNameToLowerInvariant == "notdef")
                {
                    spriteCharacter.unicode = 0;
                    spriteCharacter.name = fileNameToLowerInvariant;
                }
                else
                {
                    if (!string.IsNullOrEmpty(sprite.name) && sprite.name.Length > 2 && sprite.name[0] == '0' && (sprite.name[1] == 'x' || sprite.name[1] == 'X'))
                    {
                        spriteCharacter.unicode = (uint)TMP_TextUtilities.StringHexToInt(sprite.name.Remove(0, 2));
                    }
                    spriteCharacter.name = sprite.name;
                }
                spriteCharacter.scale = 1.0f;
                spriteCharacterTable.Add(spriteCharacter);
            }
        }
        static void PopulateSpriteTables(SpriteAtlas spriteAtlas, ref List spriteCharacterTable, ref List spriteGlyphTable)
        {
            // Get number of sprites contained in the sprite atlas.
            int spriteCount = spriteAtlas.spriteCount;
            Sprite[] sprites = new Sprite[spriteCount];
            // Get all the sprites
            spriteAtlas.GetSprites(sprites);
            for (int i = 0; i < sprites.Length; i++)
            {
                Sprite sprite = sprites[i];
                TMP_SpriteGlyph spriteGlyph = new TMP_SpriteGlyph();
                spriteGlyph.index = (uint)i;
                spriteGlyph.metrics = new GlyphMetrics(sprite.textureRect.width, sprite.textureRect.height, -sprite.pivot.x, sprite.textureRect.height - sprite.pivot.y, sprite.textureRect.width);
                spriteGlyph.glyphRect = new GlyphRect(sprite.textureRect);
                spriteGlyph.scale = 1.0f;
                spriteGlyph.sprite = sprite;
                spriteGlyphTable.Add(spriteGlyph);
                TMP_SpriteCharacter spriteCharacter = new TMP_SpriteCharacter(0xFFFE, spriteGlyph);
                spriteCharacter.name = sprite.name;
                spriteCharacter.scale = 1.0f;
                spriteCharacterTable.Add(spriteCharacter);
            }
        }
        /// 
        /// Create and add new default material to sprite asset.
        /// 
        /// 
        static void AddDefaultMaterial(TMP_SpriteAsset spriteAsset)
        {
            Shader shader = Shader.Find("TextMeshPro/Sprite");
            Material material = new Material(shader);
            material.SetTexture(ShaderUtilities.ID_MainTex, spriteAsset.spriteSheet);
            spriteAsset.material = material;
            material.name = spriteAsset.name + " Material";
            AssetDatabase.AddObjectToAsset(material, spriteAsset);
        }
        // Update existing SpriteInfo
        static List UpdateSpriteInfo(TMP_SpriteAsset spriteAsset)
        {
            //Debug.Log("Updating Sprite Asset.");
            string filePath = AssetDatabase.GetAssetPath(spriteAsset.spriteSheet);
            // Get all the Sprites sorted Left to Right / Top to Bottom
            Sprite[] sprites = AssetDatabase.LoadAllAssetsAtPath(filePath).Select(x => x as Sprite).Where(x => x != null).OrderByDescending(x => x.rect.y).ThenBy(x => x.rect.x).ToArray();
            for (int i = 0; i < sprites.Length; i++)
            {
                Sprite sprite = sprites[i];
                // Check if the sprite is already contained in the SpriteInfoList
                int index = -1;
                if (spriteAsset.spriteInfoList.Count > i && spriteAsset.spriteInfoList[i].sprite != null)
                    index = spriteAsset.spriteInfoList.FindIndex(item => item.sprite.GetInstanceID() == sprite.GetInstanceID());
                // Use existing SpriteInfo if it already exists
                TMP_Sprite spriteInfo = index == -1 ? new TMP_Sprite() : spriteAsset.spriteInfoList[index];
                Rect spriteRect = sprite.rect;
                spriteInfo.x = spriteRect.x;
                spriteInfo.y = spriteRect.y;
                spriteInfo.width = spriteRect.width;
                spriteInfo.height = spriteRect.height;
                // Get Sprite Pivot
                Vector2 pivot = new Vector2(0 - (sprite.bounds.min.x) / (sprite.bounds.extents.x * 2), 0 - (sprite.bounds.min.y) / (sprite.bounds.extents.y * 2));
                // The position of the pivot influences the Offset position.
                spriteInfo.pivot = new Vector2(0 - pivot.x * spriteRect.width, spriteRect.height - pivot.y * spriteRect.height);
                if (index == -1)
                {
                    // Find the next available index for this Sprite
                    int[] ids = spriteAsset.spriteInfoList.Select(item => item.id).ToArray();
                    int id = 0;
                    for (int j = 0; j < ids.Length; j++ )
                    {
                        if (ids[0] != 0) break;
                        if (j > 0 && (ids[j] - ids[j - 1]) > 1)
                        {
                            id = ids[j - 1] + 1;
                            break;
                        }
                        id = j + 1;
                    }
                    spriteInfo.sprite = sprite;
                    spriteInfo.name = sprite.name;
                    spriteInfo.hashCode = TMP_TextUtilities.GetSimpleHashCode(spriteInfo.name);
                    spriteInfo.id = id;
                    spriteInfo.xAdvance = spriteRect.width;
                    spriteInfo.scale = 1.0f;
                    spriteInfo.xOffset = spriteInfo.pivot.x;
                    spriteInfo.yOffset = spriteInfo.pivot.y;
                    spriteAsset.spriteInfoList.Add(spriteInfo);
                    // Sort the Sprites by ID
                    spriteAsset.spriteInfoList = spriteAsset.spriteInfoList.OrderBy(s => s.id).ToList();
                }
                else
                {
                    spriteAsset.spriteInfoList[index] = spriteInfo;
                }
            }
            return spriteAsset.spriteInfoList;
        }
    }
}