using UnityEngine;
using System.Collections.Generic;

namespace com.deeperbeige.whiteout
{
    public class RandomLevel
    {
        CreateMesh createMesh;
        GameObject gameObject;
        static int counter = 0;

        public RandomLevel (MeshParams mps)
        {
            createMesh = new CreateMesh (mps);
            createMesh.Start ();
        }

        public void addToGame ()
        {
            gameObject = new GameObject ("RandomMesh" + counter++);
            MeshFilter meshFilter = gameObject.AddComponent<MeshFilter> ();
            meshFilter.mesh = createMesh.mesh;
            gameObject.AddComponent<MeshCollider> ();
            MeshCollider meshCollider = gameObject.GetComponent<MeshCollider> ();
            meshCollider.sharedMesh = createMesh.mesh;
        }

        public void removeFromGame ()
        {
            MonoBehaviour.DestroyObject (gameObject);
        }

    }

    interface TerrainHeightFunction
    {
        float calculateFunction (float x, float z);
    }

    class BellCurveHeightFunction: TerrainHeightFunction
    {

        private float x_offset;
        private float z_offset;
        private float sqrSigma;
        private float verticalScale;
        private bool upward;
        
        public BellCurveHeightFunction (float x_offset, float z_offset, float sigma, bool upward)
        {
            this.x_offset = x_offset;
            this.z_offset = z_offset;
            this.sqrSigma = sigma * sigma;
            this.upward = upward;

            this.verticalScale = 1 / 0.7f; // we don't care about total area under curve, so make this a constant
            // otherwise it would be Mathf.Sqrt (2 * Mathf.PI * sqrSigma);
        }

        public float calculateFunction (float x, float z)
        { 
            float sqrDistance = ((x - x_offset) * (x - x_offset) + (z - z_offset) * (z - z_offset));
           
            var f = Mathf.Exp (-sqrDistance / (2 * sqrSigma)) * verticalScale;
            //x Debug.Log ("called for x;z [" + x + "," + z + "] result: " + f);
            return upward ? f : -f;
        }

        // exp(-(x-mu)^2/(2*sigma^2))/sqrt(2*pi*sigma^2)
    }

    // Based on http://studentgamedev.blogspot.no/2013/08/unity-voxel-tutorial-part-1-generating.html
    class CreateMesh
    {
        MeshParams mps;

        // This first list contains every vertex of the mesh that we are going to render
        public List<Vector3> newVertices = new List<Vector3> ();
            
        // The triangles tell Unity how to build each section of the mesh joining
        // the vertices
        public List<int> newTriangles = new List<int> ();
        
        // The UV list is unimportant right now but it tells Unity how the texture is
        // aligned on each polygon
        public List<Vector2> newUV = new List<Vector2> ();

        // A mesh is made up of the vertices, triangles and UVs we are going to define,
        // after we make them up we'll save them as this mesh
        public Mesh mesh {
            get;
            set;
        }

        private int squareCount = 0;
        public float[,] blocks;
        private float x_single_square_length;
        private float z_single_square_length;
        private List<TerrainHeightFunction> heightFunctions = new List<TerrainHeightFunction> ();

        public CreateMesh (MeshParams mps)
        {
            this.mesh = new Mesh ();       
            this.mps = mps;
            x_single_square_length = mps.width / mps.gridGranularityX;
            z_single_square_length = mps.depth / mps.gridGranularityZ;

            bool up = true;
            for (int i=0; i<mps.bumps; i++) {
                var randCentre = UnityEngine.Random.insideUnitCircle;
                heightFunctions.Add (
                    new BellCurveHeightFunction (randCentre.x * Mathf.Sqrt(mps.attenuationRadius), 
                                             randCentre.y * Mathf.Sqrt(mps.attenuationRadius),
                                    mps.bellCurveSigmaMin + 
                    (UnityEngine.Random.value) * (mps.bellCurveSigmaMax - mps.bellCurveSigmaMin), 
                                            up));
                up = !up;
            }

        }

        void GenSquare (float x, float z, float y00, float y01, float y10, float y11, float x_length, float z_length)
        {
            newVertices.Add (new Vector3 (
                x,
                y00, 
                z));
            newVertices.Add (new Vector3 (
                x + x_length,
                y10, 
                z));        
            newVertices.Add (new Vector3 (
                x + x_length,
                y11, 
                z + z_length));
            newVertices.Add (new Vector3 (
                x,
                y01,
                z + z_length));

            newUV.Add (new Vector2 (0, 0));
            newUV.Add (new Vector2 (0, 1));
            newUV.Add (new Vector2 (1, 1));
            newUV.Add (new Vector2 (1, 0));

            int currentTriangleCount = squareCount * 4;

            newTriangles.Add (currentTriangleCount + 0);
            newTriangles.Add (currentTriangleCount + 3);
            newTriangles.Add (currentTriangleCount + 1);

            newTriangles.Add (currentTriangleCount + 1);
            newTriangles.Add (currentTriangleCount + 3);
            newTriangles.Add (currentTriangleCount + 2);

            squareCount++;
        }

        public void Start ()
        {       
            GenTerrain ();
            BuildMesh ();
            UpdateMesh ();
        }

        void UpdateMesh ()
        {
            mesh.Clear ();
            mesh.vertices = newVertices.ToArray ();
            mesh.triangles = newTriangles.ToArray ();
            mesh.uv = newUV.ToArray ();
            mesh.Optimize ();
            mesh.RecalculateNormals ();
            
            squareCount = 0;
            newUV.Clear ();
            newVertices.Clear ();
            newTriangles.Clear ();
        }

        // -1 <= x,z <= 1
        float heightFunction (float x, float z)
        {
            if (Mathf.Abs (x) > 1) {
                throw new UnityException ("Ooops, x=" + x);
            }
            if (Mathf.Abs (z) > 1) {
                throw new UnityException ("Ooops, z=" + z);
            }
            float height = 0;
            foreach (TerrainHeightFunction fn in heightFunctions) {
                height += fn.calculateFunction (x, z);
            }

            return normalizeToCircle (x, z) * height;
        }

        // A function that is usually 1, but steeply falls to 0 outside of the circle (x^2 + y^2 = 1)
        float normalizeToCircle (float x, float z)
        {
            var sqrRadius = (x * x + z * z);

            float overThreshold = sqrRadius - mps.attenuationRadius * mps.attenuationRadius * 2f;
            float f = 0;
            if (overThreshold < 0) {
                f = 1.0f;
            } else {
                f = Mathf.Exp (-(overThreshold) / 2 * (mps.attenuationSigma * mps.attenuationSigma));
            }
            //    Debug.Log ("called for x;z [" + x + "," + z + "] result: " + f);
            return f;
        }
        
        void GenTerrain ()
        {
            /* Add one to each dimension becuase there's one fewer square
             * than height value (fencepost)
             */
            blocks = new float[mps.gridGranularityX + 1, mps.gridGranularityZ + 1];
            
            for (int px=0; px<blocks.GetLength(0); px++) {
                for (int pz=0; pz<blocks.GetLength(1); pz++) {
                    blocks [px, pz] = heightFunction ((((float)px) - mps.gridGranularityX / 2) / (mps.gridGranularityX / 2), 
                                                      (((float)pz) - mps.gridGranularityZ / 2) / (mps.gridGranularityZ / 2));
                }
            }
        }

        void BuildMesh ()
        {
            float x_start = -(mps.width / 2);
            float z_start = - (mps.depth / 2);
            for (int px=0; px<blocks.GetLength(0)-1; px++) {
                for (int pz=0; pz<blocks.GetLength(1)-1; pz++) {
                    GenSquare (x_start + px * x_single_square_length, z_start + pz * z_single_square_length,
                               blocks [px, pz],
                               blocks [px, pz + 1],
                               blocks [px + 1, pz],
                               blocks [px + 1, pz + 1],
                               x_single_square_length, z_single_square_length);      
                }
            }

//            for (int px=0; px<blocks.GetLength(0)-1; px++) {
//                for (int pz=0; pz<blocks.GetLength(1)-1; pz++) {
//                    Debug.Log ("px;pz [" +px + "," + pz + "]:" + blocks[px,pz]);
//                }
//            }
        }

    }

}


