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

namespace VisiMath.Fractals
{
    /// <summary>
    /// Classical Perlin noise (gradient variety latice noise)
    /// </summary>
    public class PerlinNoise
    {
        #region fields

        /// <summary>
        /// Maximal dimensions of the noise
        /// </summary>
        protected int _dimensions = 2;
        
        /// <summary>
        /// Size of permutation table
        /// </summary>
        protected int _tabSize = 256;

        protected int[] _permutations;

        protected float[] g1;
        protected Vector2[] g2;
        protected Vector3[] g3;

        protected Random rnd;

        /// <summary>
        /// Maximal dimensions of the noise
        /// </summary>
        public int Dimensions
        {
            get
            {
                return _dimensions;
            }
        }

        #endregion

        #region constructors
        /// <summary>
        /// Constructs default noise
        /// </summary>
        public PerlinNoise() : this(null, 256, 2)
        {
        }

        /// <summary>
        /// Constructs noise with specified random seed
        /// </summary>
        /// <param name="RandSeed">Randomseed</param>
        public PerlinNoise(int RandSeed)
        {
            Random rgen = new Random(RandSeed);
            Constructor(rgen, 256, 2);
        }

        /// <summary>
        /// Constructs noise with specified random generator
        /// </summary>
        /// <param name="rgen">Random generator</param>
        public PerlinNoise(Random rgen) : this (rgen, 256, 2)
        {
        }

        /// <summary>
        /// Constructs noise with random generator
        /// </summary>
        /// <param name="rgen">Random generator</param>
        /// <param name="tabsize">Size of the table (default 256) </param>
        /// <param name="dimensions">Maximal number of dimensions (1 to 3)</param>
        public PerlinNoise(Random rgen, int tabsize, int dimensions = 2)
        {
            Constructor(rgen, tabsize, dimensions);
        }

        protected void Constructor(Random rgen, int tabsize, int dimensions)
        {
            tabsize = MathTools.NearestPower2(tabsize);
            _tabSize = tabsize;

            if (rgen == null)
                rnd = new Random();
            else
                rnd = rgen;
            _dimensions = MathTools.Clamp(dimensions, 1, 3);
            PrepareTable();
        }

        #endregion

        #region layered noise
        /// <summary>
        /// returns layeres 1d noise
        /// </summary>
        /// <param name="arg">Argument</param>
        /// <param name="persistence">Persistance</param>
        /// <param name="octaves">Number of octaves</param>
        /// <returns>Noise value</returns>
        public float GetNoise(float arg, float persistence, int octaves)
        {
            float total = 0;

            for (int i = 0; i < octaves; i++)
            {
                float freq = 2 ^ i;
                float ampl = MathTools.Pow(persistence, i);

                total += ampl * GetNoise(arg * freq);
            }
            return total;
        }

        /// <summary>
        /// returns layered 2d noise
        /// </summary>
        /// <param name="arg">Argument</param>
        /// <param name="persistence">Persistance</param>
        /// <param name="octaves">Number of octaves</param>
        /// <returns>Noise value</returns>
        public float GetNoise(Vector2 arg, float persistence, int octaves)
        {
            if (_dimensions < 2)
                return float.NaN;
            return GetNoise(arg.X, arg.Y, persistence, octaves);
        }

        /// <summary>
        /// returns layered 2d noise
        /// </summary>
        /// <param name="x">x argument</param>
        /// <param name="y">y argument</param>
        /// <param name="persistence">Persistance</param>
        /// <param name="octaves">Number of octaves</param>
        /// <returns>Noise value</returns>
        public float GetNoise(float x, float y, float persistence, int octaves)
        {
            if (_dimensions < 2)
                return float.NaN;
            float total = 0;

            for (int i = 0; i < octaves; i++)
            {
                float freq = MathTools.Pow(2,i);
                float ampl = MathTools.Pow(persistence, i);

                total += ampl * GetNoise2D(x * freq, y*freq);
            }
            return total;
        }

        /// <summary>
        /// returns layered 3d noise
        /// </summary>
        /// <param name="arg">Argument</param>
        /// <param name="persistence">Persistance</param>
        /// <param name="octaves">Number of octaves</param>
        /// <returns>Noise value</returns>
        public float GetNoise(Vector3 arg, float persistence, int octaves)
        {
            if (_dimensions < 3)
                return float.NaN;
            return GetNoise(arg.X, arg.Y, arg.Z, persistence, octaves);
        }

        /// <summary>
        /// returns layered 3d noise
        /// </summary>
        /// <param name="x">x argument</param>
        /// <param name="y">y argument</param>
        /// <param name="z">z argument</param>
        /// <param name="persistence">Persistance</param>
        /// <param name="octaves">Number of octaves</param>
        /// <returns>Noise value</returns>
        public float GetNoise(float x, float y, float z, float persistence, int octaves)
        {
            if (_dimensions < 3)
                return float.NaN;
            float total = 0;

            for (int i = 0; i < octaves; i++)
            {
                float freq = 2 ^ i;
                float ampl = MathTools.Pow(persistence, i);

                total += ampl * GetNoise3D(x * freq, y * freq, z*freq);
            }
            return total;        
        }

#endregion

        #region basic noise

        /// <summary>
        /// Gets one dimensional noise
        /// </summary>
        /// <param name="arg">Argument</param>
        /// <returns>Noise value</returns>
        public float GetNoise(float arg)
        {
            //positions of surrounding points;
            int qx0 = (int)MathTools.Floor(arg);
            int qx1 = qx0 + 1;

            //permutations
            int q00 = GetPermutation(qx0);
            int q01 = GetPermutation(qx1);

            //permutate values
            float tx0 = MathTools.FractionalPart(arg);
            float tx1 = tx0 - 1;

            //read gradients
            float v00 = g1[q00] * tx0;
            float v01 = g1[q01] * tx1;

            return MathTools.Interp(tx0, v00, v01);
        }

        /// <summary>
        /// Gets one dimensional noise
        /// </summary>
        /// <param name="arg">Argument</param>
        /// <returns>Noise value</returns>
        public float GetNoise2D(Vector2 arg)
        {
            return GetNoise2D(arg.X, arg.Y);
        }

        /// <summary>
        /// Returns 2d noise
        /// </summary>
        /// <param name="argX">X argument</param>
        /// <param name="argY">Y argument</param>
        /// <returns>Noise value</returns>
        public float GetNoise2D(float argX, float argY)
        {
            if (_dimensions < 2)
            {
                return float.NaN;
            }


            //positions of surrounding points;
            int qx0 = (int)MathTools.Floor(argX);
            int qx1 = qx0 + 1;
            int qy0 = (int)MathTools.Floor(argY);
            int qy1 = qy0 + 1;


            //permutate values

            int q00 = GetPermutation(qx0, qy0);
            int q01 = GetPermutation(qx1, qy0);
            int q10 = GetPermutation(qx0, qy1);
            int q11 = GetPermutation(qx1, qy1);

            float tx0 = MathTools.FractionalPart(argX);
            float tx1 = tx0 - 1;
            float ty0 = MathTools.FractionalPart(argY);
            float ty1 = ty0 - 1;

            // read gradient vectors;

            float v00 = g2[q00].X * tx0 + g2[q00].Y * ty0;
            float v01 = g2[q01].X * tx1 + g2[q01].Y * ty0;
            float v10 = g2[q10].X * tx0 + g2[q10].Y * ty1;
            float v11 = g2[q11].X * tx1 + g2[q11].Y * ty1;

            /*
            if ((v00 > 1) || (v00 < -1) || (v01 > 1) || (v01 < -1) ||
                (v10 > 1) || (v10 < -1) || (v11 > 1) || (v11 < -1))
            {
                int bakalakada = 9;
            }*/

            float rval = MathTools.BicubInterp(tx0, ty0, v00, v01, v10, v11);
            /*if ((rval > 1) || (rval < -1))
            {
                int dakalaka = 5;
            }*/
            /*if (rval != 0)
            {
                int daka = 1;
            }*/
            return rval;
        }

        /// <summary>
        /// Returns 3d noise
        /// </summary>
        /// <param name="arg">argument</param>
        /// <returns>Noise value</returns>
        public float GetNoise3D(Vector3 arg)
        {
            return GetNoise3D(arg.X, arg.Y, arg.Z);
        }

        /// <summary>
        /// Returns 3d noise
        /// </summary>
        /// <param name="argX">x argument</param>
        /// <param name="argY">y argument</param>
        /// <param name="argZ">z argument</param>
        /// <returns>Noise value</returns>
        public float GetNoise3D(float argX, float argY, float argZ)
        {
            if (_dimensions < 3)
            {
                return float.NaN;
            }

            //positions of surrounding points;
            int qx0 = (int)MathTools.Floor(argX);
            int qx1 = qx0 + 1;
            int qy0 = (int)MathTools.Floor(argY);
            int qy1 = qy0 + 1;
            int qz0 = (int)MathTools.Floor(argZ);
            int qz1 = qz0 + 1;

            //permutate values

            int q000 = GetPermutation(qx0, qy0, qz0);
            int q010 = GetPermutation(qx1, qy0, qz0);
            int q100 = GetPermutation(qx0, qy1, qz0);
            int q110 = GetPermutation(qx1, qy1, qz0);

            int q001 = GetPermutation(qx0, qy0, qz1);
            int q011 = GetPermutation(qx1, qy0, qz1);
            int q101 = GetPermutation(qx0, qy1, qz1);
            int q111 = GetPermutation(qx1, qy1, qz1);

            float tx0 = MathTools.FractionalPart(argX);
            float tx1 = tx0 - 1;
            float ty0 = MathTools.FractionalPart(argY);
            float ty1 = ty0 - 1;
            float tz0 = MathTools.FractionalPart(argZ);
            float tz1 = tz0 - 1;

            // read gradient vectors;

            float v000 = g3[q000].X * tx0 + g3[q000].Y * ty0 + g3[q000].Z * tz0;
            float v010 = g3[q010].X * tx1 + g3[q010].Y * ty0 + g3[q010].Z * tz0;
            float v100 = g3[q100].X * tx0 + g3[q100].Y * ty1 + g3[q100].Z * tz0;
            float v110 = g3[q110].X * tx1 + g3[q110].Y * ty1 + g3[q110].Z * tz0;

            float v001 = g3[q001].X * tx0 + g3[q001].Y * ty0 + g3[q001].Z * tz1;
            float v011 = g3[q011].X * tx1 + g3[q011].Y * ty0 + g3[q011].Z * tz1;
            float v101 = g3[q101].X * tx0 + g3[q101].Y * ty1 + g3[q101].Z * tz1;
            float v111 = g3[q111].X * tx1 + g3[q111].Y * ty1 + g3[q111].Z * tz1;

            return MathTools.TricubInterp(tx0, ty0, tz0, v000, v010, v100, v110, v001, v011, v101, v111);
        }

        #endregion

        #region protected

        protected int GetPermutation(int x)
        {
            x = Math.Abs(x);
            return _permutations[x];
        }

        protected int GetPermutation(int x, int y)
        {
            x = Math.Abs(x);
            y = Math.Abs(y);
            x = x % _tabSize;

            return _permutations[(y + _permutations[x])%_tabSize];
        }

        protected int GetPermutation(int x, int y, int z)
        {
            x = Math.Abs(x);
            y = Math.Abs(y);
            y = Math.Abs(z);
            return _permutations[(z + _permutations[(y + _permutations[x % _tabSize]) % _tabSize]) % _tabSize];
        }

        /// <summary>
        /// returns random number in range -1 1
        /// </summary>
        /// <returns></returns>
        protected float GetRnd()
        {            
            return (float)(2*rnd.NextDouble() - 1);            
            //int randn = rnd.Next() % (_tabSize * 2) - _tabSize;
            //return (float)randn / (float)_tabSize;            
        }

        protected void PrepareTable()
        {
            //rnd = new Random();
            

            _permutations = new int[_tabSize];
            
            
            // prepare permutation table
            for (int i = 0; i < _tabSize; i++)
            {
                _permutations[i] = i;
            }

            for (int i = 0; i < _tabSize; i++)
            {
                MathTools.Swap(ref _permutations[i], ref _permutations[rnd.Next(_tabSize)]);
            }

            //prepare gradient vectors


            g1 = new float[_tabSize];
            if (_dimensions > 1)
                g2 = new Vector2[_tabSize];
            else g2 = null;
            if (_dimensions > 2)
                g3 = new Vector3[_tabSize];
            else g3 = null;


            for (int i = 0; i < _tabSize; i++)
            {
                g1[i] = GetRnd();
                if (_dimensions > 1)
                {
                    g2[i] = new Vector2(GetRnd(), GetRnd());
                    g2[i].Normalize();
                }
                if (_dimensions > 2)
                {
                    g3[i] = new Vector3(GetRnd(), GetRnd(), GetRnd());
                    g3[i].Normalize();
                }
            }
        }

        #endregion

    }
}
