using UnityEngine;

using PlasticGui;

namespace Unity.PlasticSCM.Editor.UI.Avatar
{
    internal static class AvatarGenerator
    {
        internal static Texture2D GenerateFromUserName(string userName)
        {
            if (string.IsNullOrEmpty(userName))
                return null;

            Font font = GetDefaultFont();

            if (font == null)
                return null;

            Color bgColor = AvatarColor.FromUserName(userName);
            string initials = GetAvatarInitial.ForUserName(userName);

            return Generate(bgColor, initials, font);
        }

        static Texture2D Generate(Color bgColor, string initials, Font font)
        {
            int fontSize = Mathf.RoundToInt(RENDER_SIZE * 0.48f);
            font.RequestCharactersInTexture(initials, fontSize, FontStyle.Normal);

            Texture2D textTexture = RenderTextToTexture(font, initials, fontSize);
            Texture2D highRes = CompositeCircleAndText(bgColor, textTexture);

            UnityEngine.Object.DestroyImmediate(textTexture, true);

            Texture2D result = Downsample(highRes);

            UnityEngine.Object.DestroyImmediate(highRes, true);

            return result;
        }

        static Texture2D RenderTextToTexture(
            Font font, string initials, int fontSize)
        {
            RenderTexture rt = RenderTexture.GetTemporary(
                RENDER_SIZE, RENDER_SIZE, 0, RenderTextureFormat.ARGB32);
            RenderTexture prev = RenderTexture.active;
            RenderTexture.active = rt;

            GL.Clear(true, true, Color.clear);
            GL.PushMatrix();
            GL.LoadPixelMatrix(0, RENDER_SIZE, RENDER_SIZE, 0);

            DrawInitials(font, initials, fontSize);

            GL.PopMatrix();

            Texture2D result = new Texture2D(
                RENDER_SIZE, RENDER_SIZE, TextureFormat.RGBA32, false);
            result.ReadPixels(new Rect(0, 0, RENDER_SIZE, RENDER_SIZE), 0, 0);
            result.Apply();

            RenderTexture.active = prev;
            RenderTexture.ReleaseTemporary(rt);

            return result;
        }

        static Texture2D CompositeCircleAndText(
            Color bgColor, Texture2D textTexture)
        {
            Color[] textPixels = textTexture.GetPixels();
            Color[] pixels = new Color[RENDER_SIZE * RENDER_SIZE];

            float center = RENDER_SIZE / 2f;
            float radius = RENDER_SIZE / 2f;

            for (int y = 0; y < RENDER_SIZE; y++)
            {
                for (int x = 0; x < RENDER_SIZE; x++)
                {
                    int idx = y * RENDER_SIZE + x;

                    float dx = x + 0.5f - center;
                    float dy = y + 0.5f - center;
                    float dist = Mathf.Sqrt(dx * dx + dy * dy);

                    float circleAlpha = Mathf.Clamp01(radius - dist + 0.5f);

                    float textCoverage = textPixels[idx].r;

                    pixels[idx] = new Color(
                        Mathf.Lerp(bgColor.r, 1f, textCoverage),
                        Mathf.Lerp(bgColor.g, 1f, textCoverage),
                        Mathf.Lerp(bgColor.b, 1f, textCoverage),
                        circleAlpha);
                }
            }

            Texture2D result = new Texture2D(
                RENDER_SIZE, RENDER_SIZE, TextureFormat.RGBA32, false);
            result.SetPixels(pixels);
            result.Apply();

            return result;
        }

        static Texture2D Downsample(Texture2D source)
        {
            int blockSize = RENDER_SIZE / OUTPUT_SIZE;
            float invBlockArea = 1f / (blockSize * blockSize);

            Color[] sourcePixels = source.GetPixels();
            Color[] targetPixels = new Color[OUTPUT_SIZE * OUTPUT_SIZE];

            for (int ty = 0; ty < OUTPUT_SIZE; ty++)
            {
                for (int tx = 0; tx < OUTPUT_SIZE; tx++)
                {
                    float r = 0, g = 0, b = 0, a = 0;

                    for (int by = 0; by < blockSize; by++)
                    {
                        int rowOffset = (ty * blockSize + by) * RENDER_SIZE;

                        for (int bx = 0; bx < blockSize; bx++)
                        {
                            Color c = sourcePixels[rowOffset + tx * blockSize + bx];
                            r += c.r;
                            g += c.g;
                            b += c.b;
                            a += c.a;
                        }
                    }

                    targetPixels[ty * OUTPUT_SIZE + tx] = new Color(
                        r * invBlockArea,
                        g * invBlockArea,
                        b * invBlockArea,
                        a * invBlockArea);
                }
            }

            Texture2D result = new Texture2D(
                OUTPUT_SIZE, OUTPUT_SIZE, TextureFormat.RGBA32, false);
            result.filterMode = FilterMode.Bilinear;
            result.SetPixels(targetPixels);
            result.Apply();

            return result;
        }

        static void DrawInitials(Font font, string initials, int fontSize)
        {
            if (font.material == null)
                return;

            font.material.SetPass(0);

            CharacterInfo[] charInfos = new CharacterInfo[initials.Length];
            float totalAdvance = 0;
            float maxAscent = 0;
            float maxDescent = 0;

            for (int i = 0; i < initials.Length; i++)
            {
                if (!font.GetCharacterInfo(
                        initials[i], out charInfos[i], fontSize, FontStyle.Normal))
                    return;

                totalAdvance += charInfos[i].advance;
                maxAscent = Mathf.Max(maxAscent, charInfos[i].maxY);
                maxDescent = Mathf.Min(maxDescent, charInfos[i].minY);
            }

            float baselineY = (RENDER_SIZE + maxAscent + maxDescent) / 2f;
            float startX = (RENDER_SIZE - totalAdvance) / 2f;

            GL.Begin(GL.QUADS);
            GL.Color(Color.white);

            float curX = startX;
            for (int i = 0; i < initials.Length; i++)
            {
                CharacterInfo ci = charInfos[i];

                float left = curX + ci.minX;
                float right = curX + ci.maxX;
                float top = baselineY - ci.maxY;
                float bottom = baselineY - ci.minY;

                GL.TexCoord2(ci.uvBottomLeft.x, ci.uvBottomLeft.y);
                GL.Vertex3(left, bottom, 0);

                GL.TexCoord2(ci.uvBottomRight.x, ci.uvBottomRight.y);
                GL.Vertex3(right, bottom, 0);

                GL.TexCoord2(ci.uvTopRight.x, ci.uvTopRight.y);
                GL.Vertex3(right, top, 0);

                GL.TexCoord2(ci.uvTopLeft.x, ci.uvTopLeft.y);
                GL.Vertex3(left, top, 0);

                curX += ci.advance;
            }

            GL.End();
        }

        static Font GetDefaultFont()
        {
            if (mFont != null)
                return mFont;

#if UNITY_2022_1_OR_NEWER
            mFont = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
#else
            mFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
#endif

            return mFont;
        }

        static Font mFont;

        const int RENDER_SIZE = 256;
        const int OUTPUT_SIZE = 32;
    }
}