using UnityEngine;
using Random = System.Random;

namespace LudumUtils.Misc {
    /// <summary>
    /// Generate a noise map using perlin noise.
    /// </summary>
    [System.Serializable]
    public class NoiseMap {
        private static Random _random = new Random();

        public readonly int Width;
        public readonly int Height;

        public readonly float XOffset;
        public readonly float YOffset;

        public readonly float Scale;

        public readonly Texture2D NoiseTex;
        public readonly Color[] Pix;

        public readonly int Seed;

        private NoiseMap(int width, int height, float xOffset, float yOffset, float scale, int seed) {
            Width = width;
            Height = height;
            XOffset = xOffset;
            YOffset = yOffset;
            Scale = scale;
            Seed = seed;

            NoiseTex = new Texture2D(Width, Height);
            Pix = new Color[NoiseTex.width * NoiseTex.height];
        }

        public static NoiseMap Calculate(int width, int height, float xOffset, float yOffset, float scale, int seed) {
            var map = new NoiseMap(width, height, xOffset, yOffset, scale, seed);
            map.CalcNoise();

            return map;
        }

        public static NoiseMap Calculate(int width, int height, float xOffset, float yOffset, float scale) {
            return Calculate(width, height, xOffset, yOffset, scale, RandomSeed());
        }

        public static NoiseMap Calculate(int width, int height, float scale, int seed) {
            return Calculate(width, height, 0, 0, scale, seed);
        }

        public static NoiseMap Calculate(int width, int height, float scale) {
            return Calculate(width, height, 0, 0, scale, RandomSeed());
        }

        public static NoiseMap Calculate(int width, int height, int seed) {
            return Calculate(width, height, 1, seed);
        }

        public static NoiseMap Calculate(int width, int height) {
            return Calculate(width, height, 1, RandomSeed());
        }

        public static int RandomSeed() {
            return _random.Next();
        }

        private void CalcNoise() {
            var y = 0f;

            while (y < NoiseTex.height) {
                var x = 0f;

                while (x < NoiseTex.width) {
                    var xCoord = XOffset + x / NoiseTex.width * Scale;
                    var yCoord = YOffset + y / NoiseTex.height * Scale;

                    // Clamping is required since the documentation states that [0, 1] may be exceeded slightly.
                    var sample = Mathf.Clamp01(Mathf.PerlinNoise(xCoord + Seed, yCoord + Seed));

                    Pix[(int) (y * NoiseTex.width + x)] = new Color(sample, sample, sample);

                    x++;
                }

                y++;
            }

            NoiseTex.SetPixels(Pix);
            NoiseTex.Apply();
        }
    }
}
