using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

#if UNITY_EDITOR
using UnityEditor;
#endif

using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
[ExecuteAlways]
public class NameTagGenerator : MonoBehaviour
{
    // Yes this is absolutely cancer, I'm sorry but it does the job :P
    private const int MCAtlasWidth = 128;
    private const int MCAtlasHeight = 128;
    private const float MCCharWidth = 8.0F * (1.0F / MCAtlasWidth);
    private const float MCCharHeight = 8.0F * (1.0F / MCAtlasHeight);

    private static float GetCharWidth(byte b)
    {
        // Wonky ASCII characters
        if (b == 0x69) // i
            return 2.0F;

        if (b == 0x6C) // l
            return 3.0F;
        
        if (b == 0x74 || b == 0x49) // t and I
            return 4.0F;

        if (b == 0x6B || b == 0x66) // k and f
            return 5.0F;
        
        // ASCII capitals and lowercase
        if (b >= 0x30 && b <= 0x39)
            return 6.0F;
        
        if (b >= 0x41 && b <= 0x5A)
            return 6.0F;
        
        if (b >= 0x61 && b <= 0x7A)
            return 6.0F;
        
        // Punctuation
        if (b == 0x21) // !
            return 2.0F;
        
        // ,.:;
        if (b == 0x3A || b == 0x3B || b == 0x2E || b == 0x2C)
            return 2.0F;

        if (b == 0x27 || b == 0x60) // '`
            return 3.0F;

        if (b == 0x7E) // ~
            return 7.0F;
        
        if (b == 0x22) // "
            return 5.0F;
        
        if (b == 0x40) // @
            return 7.0F;
        
        if (b == 0x3F) // ?
            return 7.0F;

        if (b == 0x20) // Space
            return 4.0F;

        // Misc
        if (b == 0x3E || b == 0x3C) // <>
            return 5.0F;
        
        if (b == 0x28 || b == 0x29) // ()
            return 5.0F;
        
        if (b == 0x5B || b == 0x5D) // []
            return 5.0F;
        
        if (b == 0x7B || b == 0x7D) // {}
            return 6.0F;
        
        if (b >= 0x23 && b <= 0x26) // #$%&
            return 6.0F;
        
        if (b == 0x5C || b == 0x2F) // \ and /
            return 6.0F;

        if (b == 0x5E || b == 0x5F || b == 0x3D) // ^_=
            return 6.0F;
        
        if (b == 0x5E) // ^
            return 6.0F;

        if (b == 0x2A) // *
            return 5.0F;
        
        if (b == 0x2B || b == 0x2D) // +-
            return 6.0F;
        
        if (b == 0x7C) // |
            return 2.0F;
        
        return 8.0F;
    }

    public static Mesh CreateNameTagMesh(ref Mesh mesh, string text)
    {
        mesh.Clear();
        
        List<Vector3> vertices = new List<Vector3>();
        List<Vector2> texCoords = new List<Vector2>();
        List<int> triangles = new List<int>();
        int triOffset = 0;
        
        // Begin creating our characters
        float skip = 0;
        byte[] bytes = Encoding.ASCII.GetBytes(text);
        foreach (byte b in bytes)
        {
            int column = b % 16;
            int row = b / 16;

            float charWidth = GetCharWidth(b) / 8.0F;
            
            Vector2 charMin = new Vector2(MCCharWidth * column, MCCharHeight * row);
            Vector2 charMax = charMin + new Vector2(MCCharWidth * charWidth, MCCharHeight);
            
            // Invert the UVs
            charMin.y = 1.0F - charMin.y;
            charMax.y = 1.0F - charMax.y;
            
            vertices.Add(new Vector3(skip, 0, 0));
            vertices.Add(new Vector3(skip, 1, 0));
            vertices.Add(new Vector3(skip + charWidth, 0, 0));
            vertices.Add(new Vector3(skip + charWidth, 1, 0));
            
            texCoords.Add(new Vector2(charMin.x, charMax.y));
            texCoords.Add(new Vector2(charMin.x, charMin.y));
            texCoords.Add(new Vector2(charMax.x, charMax.y));
            texCoords.Add(new Vector2(charMax.x, charMin.y));
            
            triangles.Add(triOffset + 0);
            triangles.Add(triOffset + 1);
            triangles.Add(triOffset + 2);
            triangles.Add(triOffset + 2);
            triangles.Add(triOffset + 1);
            triangles.Add(triOffset + 3);

            triOffset += 4;
            skip += charWidth;
        }
        
        // Create our enclosing backplane
        /*
        vertices.Add(new Vector3(-0.1F, -0.1F, 0));
        vertices.Add(new Vector3(-0.1F, 1.1F, 0));
        vertices.Add(new Vector3(skip + 0.1F, -0.1F, 0));
        vertices.Add(new Vector3(skip + 0.1F, 1.1F, 0));
        
        Vector2 oob = new Vector2(-1, -1);
        
        texCoords.Add(oob);
        texCoords.Add(oob);
        texCoords.Add(oob);
        texCoords.Add(oob);

        triangles.Add(triOffset + 0);
        triangles.Add(triOffset + 1);
        triangles.Add(triOffset + 2);
        triangles.Add(triOffset + 2);
        triangles.Add(triOffset + 1);
        triangles.Add(triOffset + 3);
        */

        // Now center it all!
        for (int v = 0; v < vertices.Count; v++)
        {
            Vector3 vert = vertices[v];
            
            vert.x -= skip / 2.0F;
            vert.y -= 0.5F;
            
            vertices[v] = vert;
        }
        
        mesh.SetVertices(vertices);
        mesh.SetUVs(0, texCoords);
        mesh.SetTriangles(triangles, 0);

        mesh.RecalculateBounds();
        mesh.RecalculateNormals();
        
        return mesh;
    }
    
    public string Text;

    [HideInInspector] public bool updateOnValidate = false;
    [HideInInspector] public string CachePath;
    [HideInInspector] public Mesh CacheMesh;

    public void OnValidate()
    {
        if (updateOnValidate)
            Generate();
    }
    
#if UNITY_EDITOR
    public void TryCacheMesh()
    {
        CacheMesh = new Mesh();
        CacheMesh.name = $"MCNameTagMesh: {Text}";

        CachePath = $"Assets/MCNameTagMesh_{GUID.Generate().ToString()}.asset";
        
        AssetDatabase.CreateAsset(CacheMesh, CachePath);
        AssetDatabase.SaveAssets();
    }
#endif

    public void Generate()
    {
#if UNITY_EDITOR
        if (CacheMesh == null)
            TryCacheMesh();
#endif
        
        CreateNameTagMesh(ref CacheMesh, Text);
        
#if UNITY_EDITOR
        EditorUtility.SetDirty(CacheMesh);
#endif
        
        MeshFilter filter = GetComponent<MeshFilter>();
        filter.sharedMesh = CacheMesh;
    }
}
