using UnityEngine;
using UnityEditor;

public class SDFGeneratorWindow : EditorWindow
{
   private const float MIN_WINDOW_WIDTH = 400f;
   private const float MIN_WINDOW_HEIGHT = 600f;

   private Texture2D sourceImage;
   private float threshold = 0.5f;
   private string savePath = "Assets/_Pointless Assets/Holo Ankle Band V2/GeneratedSDF.png";
   private Texture2D generatedSDF;
   private Vector2 scrollPosition;

   [MenuItem("POINTLESS/SDF Generator")]
   public static void ShowWindow()
   {
       var window = GetWindow<SDFGeneratorWindow>("SDF Generator");
       window.minSize = new Vector2(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
       window.maxSize = new Vector2(800f, 1000f);
   }

   private void OnGUI()
   {
       scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

       DrawHeader();
       DrawSettings();
       DrawPreview();
       DrawGenerateButton();

       EditorGUILayout.EndScrollView();
   }

   private void DrawHeader()
   {
       EditorGUILayout.Space(10);
       GUILayout.Label("SDF Generator", EditorStyles.boldLabel);
       EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
       EditorGUILayout.Space(5);
   }

   private void DrawSettings()
   {
       EditorGUILayout.BeginVertical(EditorStyles.helpBox);
       
       EditorGUILayout.LabelField("Input Settings", EditorStyles.boldLabel);
       EditorGUI.BeginChangeCheck();
       sourceImage = (Texture2D)EditorGUILayout.ObjectField("Source Image", sourceImage, typeof(Texture2D), false);
       
       if (sourceImage != null && !IsReadable(sourceImage))
       {
           EditorGUILayout.HelpBox("Source texture must be marked as readable. Please enable 'Read/Write Enabled' in the texture import settings.", MessageType.Warning);
       }

       EditorGUILayout.Space(5);
       threshold = EditorGUILayout.Slider("Threshold", threshold, 0f, 1f);
       
       EditorGUILayout.Space(5);
       EditorGUILayout.LabelField("Output Settings", EditorStyles.boldLabel);
       savePath = EditorGUILayout.TextField("Save Path", savePath);
       
       EditorGUILayout.EndVertical();
   }

   private void DrawPreview()
   {
       if (generatedSDF != null)
       {
           EditorGUILayout.Space(10);
           EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
           EditorGUILayout.BeginVertical(EditorStyles.helpBox);
           
           float previewSize = Mathf.Min(position.width - 40, 300);
           Rect previewRect = GUILayoutUtility.GetRect(previewSize, previewSize);
           EditorGUI.DrawPreviewTexture(previewRect, generatedSDF);
           
           EditorGUILayout.EndVertical();
       }
   }

   private void DrawGenerateButton()
   {
       EditorGUILayout.Space(10);
       GUI.enabled = sourceImage != null && IsReadable(sourceImage);
       
       if (GUILayout.Button("Generate SDF", GUILayout.Height(30)))
       {
           GenerateAndSaveSDF();
       }
       
       GUI.enabled = true;
   }

   private bool IsReadable(Texture2D texture)
   {
       try
       {
           texture.GetPixel(0, 0);
           return true;
       }
       catch
       {
           return false;
       }
   }

    private void GenerateAndSaveSDF()
    {
        SDFGenerator generator = new SDFGenerator();
        generator.sourceImage = sourceImage;
        generator.threshold = threshold;

        generatedSDF = generator.GenerateSDF();

        byte[] bytes = generatedSDF.EncodeToPNG();
        System.IO.File.WriteAllBytes(savePath, bytes);
        AssetDatabase.Refresh();

        string relativePath = savePath;
        if (savePath.StartsWith(Application.dataPath))
        {
            relativePath = "Assets" + savePath.Substring(Application.dataPath.Length);
        }

        TextureImporter importer = AssetImporter.GetAtPath(relativePath) as TextureImporter;
        if (importer != null)
        {
            importer.textureType = TextureImporterType.Default;
            importer.sRGBTexture = false;
            importer.filterMode = FilterMode.Bilinear;
            importer.wrapMode = TextureWrapMode.Repeat;
            importer.maxTextureSize = 512;
            importer.textureCompression = TextureImporterCompression.CompressedHQ;
            
            importer.SaveAndReimport();
        }

        Debug.Log($"SDF generated and saved to: {savePath}");
    }
}

public class SDFGenerator
{
   public Texture2D sourceImage;
   public float threshold = 0.5f;
   
   private int width;
   private int height;
   private bool[,] binaryImage;
   private float[,] sdf;
   
   public Texture2D GenerateSDF()
   {
       if (sourceImage == null) return null;
       
       width = sourceImage.width;
       height = sourceImage.height;
       binaryImage = new bool[width, height];
       sdf = new float[width, height];
       
       for (int x = 0; x < width; x++)
       {
           for (int y = 0; y < height; y++)
           {
               Color pixel = sourceImage.GetPixel(x, y);
               binaryImage[x, y] = pixel.grayscale > threshold;
           }
       }
       
       CalculateDistances();
       
       Texture2D outputTexture = new Texture2D(width, height);
       for (int x = 0; x < width; x++)
       {
           for (int y = 0; y < height; y++)
           {
               float value = sdf[x, y];
               outputTexture.SetPixel(x, y, new Color(value, value, value, 1));
           }
       }
       
       outputTexture.Apply();
       return outputTexture;
   }
   
   private void CalculateDistances()
   {
       float maxDistance = Mathf.Sqrt(width * width + height * height);
       float[,] innerSDF = new float[width, height];
       float[,] outerSDF = new float[width, height];
       
       // Initialize both SDFs
       for (int x = 0; x < width; x++)
       {
           for (int y = 0; y < height; y++)
           {
               innerSDF[x, y] = maxDistance;
               outerSDF[x, y] = maxDistance;
           }
       }
       
       // Calculate distances for both inside and outside
       for (int x = 0; x < width; x++)
       {
           for (int y = 0; y < height; y++)
           {
               if (IsEdgePixel(x, y))
               {
                   innerSDF[x, y] = 0;
                   outerSDF[x, y] = 0;
               }
           }
       }
       
       // Forward pass for both
       for (int x = 0; x < width; x++)
       {
           for (int y = 0; y < height; y++)
           {
               if (x > 0)
               {
                   innerSDF[x, y] = Mathf.Min(innerSDF[x, y], innerSDF[x - 1, y] + 1);
                   outerSDF[x, y] = Mathf.Min(outerSDF[x, y], outerSDF[x - 1, y] + 1);
               }
               if (y > 0)
               {
                   innerSDF[x, y] = Mathf.Min(innerSDF[x, y], innerSDF[x, y - 1] + 1);
                   outerSDF[x, y] = Mathf.Min(outerSDF[x, y], outerSDF[x, y - 1] + 1);
               }
               if (x > 0 && y > 0)
               {
                   innerSDF[x, y] = Mathf.Min(innerSDF[x, y], innerSDF[x - 1, y - 1] + 1.4142f);
                   outerSDF[x, y] = Mathf.Min(outerSDF[x, y], outerSDF[x - 1, y - 1] + 1.4142f);
               }
           }
       }
       
       // Backward pass for both
       for (int x = width - 1; x >= 0; x--)
       {
           for (int y = height - 1; y >= 0; y--)
           {
               if (x < width - 1)
               {
                   innerSDF[x, y] = Mathf.Min(innerSDF[x, y], innerSDF[x + 1, y] + 1);
                   outerSDF[x, y] = Mathf.Min(outerSDF[x, y], outerSDF[x + 1, y] + 1);
               }
               if (y < height - 1)
               {
                   innerSDF[x, y] = Mathf.Min(innerSDF[x, y], innerSDF[x, y + 1] + 1);
                   outerSDF[x, y] = Mathf.Min(outerSDF[x, y], outerSDF[x, y + 1] + 1);
               }
               if (x < width - 1 && y < height - 1)
               {
                   innerSDF[x, y] = Mathf.Min(innerSDF[x, y], innerSDF[x + 1, y + 1] + 1.4142f);
                   outerSDF[x, y] = Mathf.Min(outerSDF[x, y], outerSDF[x + 1, y + 1] + 1.4142f);
               }
           }
       }

       // Find the maximum inner distance first
       float maxInner = 0f;
       for (int x = 0; x < width; x++)
       {
           for (int y = 0; y < height; y++)
           {
               if (binaryImage[x, y])
               {
                   maxInner = Mathf.Max(maxInner, innerSDF[x, y]);
               }
           }
       }

       // Now normalize both inner and outer using the same maxInner value
       for (int x = 0; x < width; x++)
       {
           for (int y = 0; y < height; y++)
           {
               if (binaryImage[x, y])
               {
                   // Inside text: map from 0.5 to 1
                   sdf[x, y] = 0.5f + (0.5f * innerSDF[x, y] / maxInner);
               }
               else
               {
                   // Outside text: map from 0 to 0.5, using same maxInner
                   float normalizedDist = Mathf.Min(outerSDF[x, y] / maxInner, 1f);
                   sdf[x, y] = 0.5f - (0.5f * normalizedDist);
               }
           }
       }
   }
   
   private bool IsEdgePixel(int x, int y)
   {
       bool isTarget = binaryImage[x, y];
       
       for (int dx = -1; dx <= 1; dx++)
       {
           for (int dy = -1; dy <= 1; dy++)
           {
               if (dx == 0 && dy == 0) continue;
               
               int nx = x + dx;
               int ny = y + dy;
               
               if (nx >= 0 && nx < width && ny >= 0 && ny < height)
               {
                   if (binaryImage[nx, ny] != isTarget)
                   {
                       return true;
                   }
               }
           }
       }
       
       return false;
   }
}