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

namespace VisiMath.Fractals
{
    /// <summary>
    /// Mid displacement fractal generator
    /// </summary>
    public class MidDisplacement : HeightMap
    {
        /// <summary>
        /// Generated data
        /// </summary>
        public float[,] Data;

        /// <summary>
        /// Mean height
        /// </summary>
        public float MeanHeight;

        /// <summary>
        /// Basic displace parameter
        /// </summary>
        public float BasicDisplace;



        /// <summary>
        /// Index like acessor
        /// </summary>
        /// <param name="indexX"></param>
        /// <param name="indexY"></param>
        /// <returns></returns>
        public override float this[float indexX, float indexY]
        {
            get
            {
                if ((indexX < 0) || (indexX>=_width) || (indexY < 0) || (indexY > _height))
                    return 0;

                return Data[(int)indexX, (int)indexY];
            }

            set
            {
                if ((indexX < 0) || (indexX>=_width) || (indexY < 0) || (indexY > _height))
                    return;

                Data[(int)indexX, (int)indexY] = value;
            }
        }



        /// <summary>
        /// Creates middisplacement fractal with 128x128 size
        /// </summary>
        public MidDisplacement() : this(128, 128)
        {
        }

        /// <summary>
        /// Creates middsiplacement fractal with specified size
        /// </summary>
        /// <param name="w">Width</param>
        /// <param name="h">Height</param>
        public MidDisplacement(int w, int h)
            : this(null, w, h)
        {
        }

        /// <summary>
        /// Creates middisplacement fractal with specified size and specific random seed
        /// </summary>
        /// <param name="RandSeed">Random seed for pseudorandom generator</param>
        /// <param name="w">Width</param>
        /// <param name="h">Height</param>
        public MidDisplacement(int RandSeed, int w, int h)
        {
            BaseConstruct(new Random(RandSeed), w, h);
        }


        /// <summary>
        /// Creates middisplacement fractal with specified size from specified random generator
        /// </summary>
        /// <param name="w">Width</param>
        /// <param name="h">Height</param>
        /// <param name="rndm">Random generator (if null, new will be constructed)</param>
        public MidDisplacement(Random rndm, int w, int h)
        {
            BaseConstruct(rndm, w, h);
        }

        /// <summary>
        /// Creates middisplacement fractal with specified size from specified random generator
        /// </summary>
        /// <param name="w">Width</param>
        /// <param name="h">Height</param>
        /// <param name="rndm">Random generator (if null, new will be constructed)</param>
        /// <param name="basedisp">Basic displace parameter</param>
        /// <param name="meanheight">Mean height of the fractal</param>
        public MidDisplacement(Random rndm, int w, int h, float meanheight, float basedisp)
        {
            BaseConstruct(rndm, w, h, meanheight, basedisp);
        }


        protected void BaseConstruct(Random rndm, int w, int h, float meanh = 0.0f, float baseDisp = 50.0f)
        {
            Data = new float[w, h];
            _width = w;
            _height = h;
            if (rndm != null)
                rnd = rndm;
            else rnd = new Random();
            MeanHeight = meanh;
            BasicDisplace = baseDisp;

            RecalcPlasma();
        }

        /// <summary>
        /// Recalculates fractal
        /// </summary>
        public void RecalcPlasma()
        {
            CalcPlasma(0, 0, _width, _height, GetBaseCorner(), GetBaseCorner(), GetBaseCorner(), GetBaseCorner());
        }

        protected float GetRandom(float max)
        {
            return ((float)(rnd.NextDouble() - 1.0)) * 2 * max;
        }

        protected float GetBaseCorner()
        {
            return MeanHeight + BasicDisplace + GetRandom(BasicDisplace);
        }

        protected float Displace(float val)
        {
            float rn = BasicDisplace * val / (_width + _height);
            return GetRandom(rn);
        }


        protected void CalcPlasma(float x1, float y1, float width, float height,
            float val1, float val2, float val3, float val4)
        {
            float neww = 0.5f * width;
            float newh = 0.5f * height;
            if ((width > 1) || (height > 1))
            {
                float mid = Displace(neww + newh) + (val1 + val2 + val3 + val4) / 4;
                mid = MathTools.Clamp(mid, 0, 200);
                float edge1 = (val1 + val2) / 2;
                float edge2 = (val2 + val3) / 2;
                float edge3 = (val3 + val4) / 2;
                float edge4 = (val1 + val4) / 2;
                CalcPlasma(x1, y1, neww, newh, val1, edge1, mid, edge4);
                CalcPlasma(x1 + neww, y1, neww, newh, edge1, val2, edge2, mid);
                CalcPlasma(x1 + neww, y1 + newh, neww, newh, mid, edge2, val3, edge3);
                CalcPlasma(x1, y1 + newh, neww, newh, edge4, mid, edge3, val4);
            }
            else
            {
                float total = (val1 + val2 + val3 + val4) / 4.0f;
                Data[(int)x1, (int)y1] = total;
            }
        }


    }
}
