using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

namespace InfiniteWorld
{
    public static class ProceduralWorldArt
    {
        private const int TextureSize = 32;
        private static readonly Dictionary<string, Sprite> SpriteCache = new Dictionary<string, Sprite>();
        private static readonly Dictionary<string, Tile> TileCache = new Dictionary<string, Tile>();

        public static Sprite CreatePlayerSprite()
        {
            const string key = "player_sprite";
            if (SpriteCache.TryGetValue(key, out Sprite cached))
            {
                return cached;
            }

            Color transparent = new Color(0f, 0f, 0f, 0f);
            Texture2D texture = CreateTexture(key, transparent);
            Color cloak = new Color(0.13f, 0.33f, 0.79f, 1f);
            Color trim = new Color(0.85f, 0.92f, 1f, 1f);
            Color face = new Color(0.99f, 0.89f, 0.72f, 1f);

            FillRect(texture, 10, 4, 12, 16, cloak);
            FillRect(texture, 9, 18, 14, 7, cloak);
            FillRect(texture, 12, 24, 8, 5, face);
            FillRect(texture, 11, 23, 10, 1, trim);
            FillRect(texture, 8, 12, 3, 10, trim);
            FillRect(texture, 21, 12, 3, 10, trim);
            FillRect(texture, 12, 0, 3, 4, cloak);
            FillRect(texture, 17, 0, 3, 4, cloak);
            texture.Apply();

            Sprite sprite = CreateSprite(texture);
            SpriteCache[key] = sprite;
            return sprite;
        }

        public static TileBase CreateSolidTile(string key, Color color, float noiseStrength = 0.04f)
        {
            if (TileCache.TryGetValue(key, out Tile cached))
            {
                return cached;
            }

            Texture2D texture = CreateTexture(key, color);
            ApplyNoise(texture, noiseStrength, 31);
            texture.Apply();
            Tile tile = CreateTile(texture, key);
            TileCache[key] = tile;
            return tile;
        }

        public static TileBase CreateFeatureTile(string key, Color fill, Color border, bool top, bool right, bool bottom, bool left, InnerCornerMask innerCornerMask = InnerCornerMask.None, bool solidCollider = false)
        {
            if (TileCache.TryGetValue(key, out Tile cached))
            {
                return cached;
            }

            Texture2D texture = CreateTexture(key, fill);
            ApplyNoise(texture, 0.05f, 71);
            DrawBorder(texture, top, right, bottom, left, border, 4);
            CarveInnerCorner(texture, innerCornerMask);
            texture.Apply();

            Tile tile = CreateTile(texture, key);
            if (solidCollider)
            {
                tile.colliderType = Tile.ColliderType.Grid;
            }
            TileCache[key] = tile;
            return tile;
        }

        public static TileBase CreateDecorationTile(string key, Color baseColor, Color accentColor, DecorationPattern pattern)
        {
            if (TileCache.TryGetValue(key, out Tile cached))
            {
                return cached;
            }

            Color transparent = new Color(0f, 0f, 0f, 0f);
            Texture2D texture = CreateTexture(key, transparent);

            switch (pattern)
            {
                case DecorationPattern.Bush:
                    DrawDisc(texture, 16, 15, 8, baseColor);
                    DrawDisc(texture, 10, 13, 6, baseColor);
                    DrawDisc(texture, 22, 13, 6, baseColor);
                    DrawDisc(texture, 14, 17, 3, accentColor);
                    DrawDisc(texture, 21, 17, 3, accentColor);
                    break;
                case DecorationPattern.Flower:
                    DrawDisc(texture, 16, 12, 2, baseColor);
                    DrawDisc(texture, 12, 16, 2, baseColor);
                    DrawDisc(texture, 20, 16, 2, baseColor);
                    DrawDisc(texture, 16, 20, 2, baseColor);
                    DrawDisc(texture, 16, 16, 2, accentColor);
                    DrawLine(texture, 16, 0, 16, 12, new Color(0.2f, 0.5f, 0.2f, 1f));
                    break;
                default:
                    DrawDisc(texture, 12, 12, 4, baseColor);
                    DrawDisc(texture, 20, 18, 3, accentColor);
                    DrawDisc(texture, 9, 20, 2, accentColor);
                    break;
            }

            texture.Apply();
            Tile tile = CreateTile(texture, key);
            TileCache[key] = tile;
            return tile;
        }

        private static Texture2D CreateTexture(string key, Color fillColor)
        {
            Texture2D texture = new Texture2D(TextureSize, TextureSize, TextureFormat.RGBA32, false)
            {
                filterMode = FilterMode.Point,
                wrapMode = TextureWrapMode.Clamp,
                name = key
            };

            Color[] pixels = new Color[TextureSize * TextureSize];
            for (int i = 0; i < pixels.Length; i++)
            {
                pixels[i] = fillColor;
            }

            texture.SetPixels(pixels);
            return texture;
        }

        private static Tile CreateTile(Texture2D texture, string key)
        {
            Sprite sprite = CreateSprite(texture);
            Tile tile = ScriptableObject.CreateInstance<Tile>();
            tile.sprite = sprite;
            tile.name = key;
            tile.hideFlags = HideFlags.HideAndDontSave;
            return tile;
        }

        private static Sprite CreateSprite(Texture2D texture)
        {
            Sprite sprite = Sprite.Create(texture, new Rect(0f, 0f, texture.width, texture.height), new Vector2(0.5f, 0.5f), TextureSize);
            sprite.name = texture.name;
            return sprite;
        }

        private static void ApplyNoise(Texture2D texture, float strength, int offset)
        {
            if (strength <= 0f)
            {
                return;
            }

            for (int y = 0; y < texture.height; y++)
            {
                for (int x = 0; x < texture.width; x++)
                {
                    Color pixel = texture.GetPixel(x, y);
                    if (pixel.a <= 0f)
                    {
                        continue;
                    }

                    float noise = Mathf.PerlinNoise((x + offset) * 0.21f, (y + offset) * 0.21f) - 0.5f;
                    texture.SetPixel(x, y, pixel * (1f + noise * strength * 2f));
                }
            }
        }

        private static void DrawBorder(Texture2D texture, bool top, bool right, bool bottom, bool left, Color color, int thickness)
        {
            if (top)
            {
                FillRect(texture, 0, TextureSize - thickness, TextureSize, thickness, color);
            }

            if (bottom)
            {
                FillRect(texture, 0, 0, TextureSize, thickness, color);
            }

            if (left)
            {
                FillRect(texture, 0, 0, thickness, TextureSize, color);
            }

            if (right)
            {
                FillRect(texture, TextureSize - thickness, 0, thickness, TextureSize, color);
            }
        }

        private static void CarveInnerCorner(Texture2D texture, InnerCornerMask innerCornerMask)
        {
            if (innerCornerMask == InnerCornerMask.None)
            {
                return;
            }

            Color transparent = new Color(0f, 0f, 0f, 0f);
            const int radius = 8;

            if ((innerCornerMask & InnerCornerMask.TopLeft) != 0)
            {
                CutCorner(texture, 0, TextureSize - 1, radius, transparent, 1, -1);
            }

            if ((innerCornerMask & InnerCornerMask.TopRight) != 0)
            {
                CutCorner(texture, TextureSize - 1, TextureSize - 1, radius, transparent, -1, -1);
            }

            if ((innerCornerMask & InnerCornerMask.BottomRight) != 0)
            {
                CutCorner(texture, TextureSize - 1, 0, radius, transparent, -1, 1);
            }

            if ((innerCornerMask & InnerCornerMask.BottomLeft) != 0)
            {
                CutCorner(texture, 0, 0, radius, transparent, 1, 1);
            }
        }

        private static void CutCorner(Texture2D texture, int originX, int originY, int radius, Color transparent, int dirX, int dirY)
        {
            for (int x = 0; x < radius; x++)
            {
                for (int y = 0; y < radius; y++)
                {
                    float dx = x / (float)radius;
                    float dy = y / (float)radius;
                    if (dx * dx + dy * dy > 1f)
                    {
                        continue;
                    }

                    int px = originX + x * dirX;
                    int py = originY + y * dirY;
                    if (px >= 0 && px < TextureSize && py >= 0 && py < TextureSize)
                    {
                        texture.SetPixel(px, py, transparent);
                    }
                }
            }
        }

        private static void DrawDisc(Texture2D texture, int centerX, int centerY, int radius, Color color)
        {
            int sqrRadius = radius * radius;
            for (int y = -radius; y <= radius; y++)
            {
                for (int x = -radius; x <= radius; x++)
                {
                    if (x * x + y * y > sqrRadius)
                    {
                        continue;
                    }

                    int px = centerX + x;
                    int py = centerY + y;
                    if (px >= 0 && px < TextureSize && py >= 0 && py < TextureSize)
                    {
                        texture.SetPixel(px, py, color);
                    }
                }
            }
        }

        private static void DrawLine(Texture2D texture, int x0, int y0, int x1, int y1, Color color)
        {
            int dx = Mathf.Abs(x1 - x0);
            int sx = x0 < x1 ? 1 : -1;
            int dy = -Mathf.Abs(y1 - y0);
            int sy = y0 < y1 ? 1 : -1;
            int error = dx + dy;

            while (true)
            {
                if (x0 >= 0 && x0 < TextureSize && y0 >= 0 && y0 < TextureSize)
                {
                    texture.SetPixel(x0, y0, color);
                }

                if (x0 == x1 && y0 == y1)
                {
                    break;
                }

                int twiceError = 2 * error;
                if (twiceError >= dy)
                {
                    error += dy;
                    x0 += sx;
                }

                if (twiceError <= dx)
                {
                    error += dx;
                    y0 += sy;
                }
            }
        }

        private static void FillRect(Texture2D texture, int x, int y, int width, int height, Color color)
        {
            for (int py = y; py < y + height; py++)
            {
                if (py < 0 || py >= TextureSize)
                {
                    continue;
                }

                for (int px = x; px < x + width; px++)
                {
                    if (px < 0 || px >= TextureSize)
                    {
                        continue;
                    }

                    texture.SetPixel(px, py, color);
                }
            }
        }
    }

    public enum DecorationPattern
    {
        Bush,
        Flower,
        Rocks
    }

    [System.Flags]
    public enum InnerCornerMask
    {
        None = 0,
        TopLeft = 1,
        TopRight = 2,
        BottomRight = 4,
        BottomLeft = 8
    }
}
