using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics.PackedVector;

namespace FredriksdalAssault2
{

    /// <summary>
    /// Drawable game component for water rendering. Renders the scene to reflection and refraction
    /// maps that are projected onto the water plane and are distorted based on two scrolling normal
    /// maps.
    /// </summary>
    public class Water
    {
        #region Variables

        // Content manager and graphics device.
        ContentManager myContentManager;
        GraphicsDevice myGraphicsDevice;

        //vertex and index buffers for the water plane
        private VertexBuffer mVertexBuffer;
        private IndexBuffer mIndexBuffer;

        //water shader
        private Effect mEffect;

        //camera properties
        public Matrix World;

        // Refraction and reflection map.
        public Texture2D refractionMap;
        public Texture2D reflectionMap;

        //scrolling normal maps that we will use as a
        //a normal for the water plane in the shader
        private Texture mWaveMap0;
        private Texture mWaveMap1;

        //user specified options to configure the water object
        //the number of vertices in the x and z plane (must be of the form 2^n + 1)
        //and the amount of spacing between vertices
        public int Width = 65;
        public int Height = 65;
        public float CellSpacing = 300f;

        //how large to scale the wave map texture in the shader
        //higher than 1 and the texture will repeat providing finer detail normals
        public float WaveMapScale = 2f;

        //offsets for the texcoords of the wave maps updated every frame
        //these are used in combination with the velocities to scroll
        //the normal maps over the water plane. Resulting in the appearence
        //of moving ripples across the water plane
        public Vector2 WaveMapOffset0;
        public Vector2 WaveMapOffset1;

        //the direction to offset the texcoords of the wave maps
        public Vector2 WaveMapVelocity0;
        public Vector2 WaveMapVelocity1;

        //water color and sun light properties
        public Vector4 WaterColor;
        public Vector4 SunColor;
        public Vector3 SunDirection;
        public float SunFactor; //the intensity of the sun specular term.
        public float SunPower;  //how shiny we want the sun specular term on the water to be.
        private int mNumVertices;
        private int mNumTris;
        
        #endregion

        /// <summary>
        /// Create new water.
        /// </summary>
        /// <param name="myContentManager"></param>
        /// <param name="myGraphicsDevice"></param>
        public Water(ContentManager myContentManager, GraphicsDevice myGraphicsDevice, Vector3 position)
        {
            // Set content manager and graphics device
            this.myContentManager = myContentManager;
            this.myGraphicsDevice = myGraphicsDevice;

            // Set water options
            this.WaveMapVelocity0 = new Vector2(0.01f, 0.01f);
            this.WaveMapVelocity1 = new Vector2(-0.01f, 0.01f);
            this.WaterColor = new Vector4(0.737f, 0.831f, 0.901f, 1.0f);
            this.SunColor = new Vector4(1.0f, 0.8f, 0.4f, 1.0f);
            this.SunDirection = new Vector3(-5.6f, -1.0f, -20.5f);
            this.SunFactor = 6f; //3f
            this.SunPower = 250.0f;

            // Set the water level under the terrain, Y:-450
            this.World = Matrix.CreateTranslation(position);

            // Initialize and load
            Initialize();
            LoadContent();

        }

        public void Initialize()
        {

            //build the water mesh
            mNumVertices = this.Width * this.Height;
            mNumTris = (this.Width - 1) * (this.Height - 1) * 2;
            VertexPositionTexture[] vertices = new VertexPositionTexture[mNumVertices];

            Vector3[] verts;
            int[] indices;

            //create the water vertex grid positions and indices
            GenTriGrid(this.Height, this.Width, this.CellSpacing, this.CellSpacing,
                        Vector3.Zero, out verts, out indices);

            //copy the verts into our PositionTextured array
            for (int i = 0; i < this.Width; ++i)
            {
                for (int j = 0; j < this.Height; ++j)
                {
                    int index = i * this.Width + j;
                    vertices[index].Position = verts[index];
                    vertices[index].TextureCoordinate = new Vector2((float)j / this.Width, (float)i / this.Height);
                }
            }

            mVertexBuffer = new VertexBuffer(this.myGraphicsDevice,
                                             VertexPositionTexture.VertexDeclaration, vertices.Length,
                                             BufferUsage.None);
            mVertexBuffer.SetData(vertices);

            mIndexBuffer = new IndexBuffer(this.myGraphicsDevice, typeof(int), indices.Length, BufferUsage.None);
            mIndexBuffer.SetData(indices);

        }

        protected void LoadContent()
        {

            // Load wave maps and textures.
            mWaveMap0 = myContentManager.Load<Texture2D>("Textures/wave0");
            mWaveMap1 = myContentManager.Load<Texture2D>("Textures/wave1");
            refractionMap = myContentManager.Load<Texture2D>("Textures/sandRefraction"); 
            reflectionMap = myContentManager.Load<Texture2D>("Textures/skyReflection");

            // Load the effect file.
            mEffect = myContentManager.Load<Effect>("Effects/Water");

            // Set parameters that not will change
            if (mEffect != null)
            {
                mEffect.CurrentTechnique = mEffect.Techniques["WaterTech"];
                mEffect.Parameters["WaveMap0"].SetValue(mWaveMap0);
                mEffect.Parameters["WaveMap1"].SetValue(mWaveMap1);
                mEffect.Parameters["TexScale"].SetValue(this.WaveMapScale);
                mEffect.Parameters["WaterColor"].SetValue(this.WaterColor);
                mEffect.Parameters["SunColor"].SetValue(this.SunColor);
                mEffect.Parameters["SunDirection"].SetValue(Vector3.Normalize(this.SunDirection));
                mEffect.Parameters["SunFactor"].SetValue(this.SunFactor);
                mEffect.Parameters["SunPower"].SetValue(this.SunPower);
                mEffect.Parameters["World"].SetValue(World);

                mEffect.Parameters["ReflectMap"].SetValue(reflectionMap);
                mEffect.Parameters["RefractMap"].SetValue(refractionMap);
            }
 
        }

        public void Update(GameTime gameTime)
        {
            float timeDelta = (float)gameTime.ElapsedGameTime.TotalSeconds;

            //update the wave map offsets so that they will scroll across the water
            this.WaveMapOffset0 += this.WaveMapVelocity0 * timeDelta;
            this.WaveMapOffset1 += this.WaveMapVelocity1 * timeDelta;

            if (this.WaveMapOffset0.X >= 1.0f || this.WaveMapOffset0.X <= -1.0f)
                this.WaveMapOffset0.X = 0.0f;
            if (this.WaveMapOffset1.X >= 1.0f || this.WaveMapOffset1.X <= -1.0f)
                this.WaveMapOffset1.X = 0.0f;
            if (this.WaveMapOffset0.Y >= 1.0f || this.WaveMapOffset0.Y <= -1.0f)
                this.WaveMapOffset0.Y = 0.0f;
            if (this.WaveMapOffset1.Y >= 1.0f || this.WaveMapOffset1.Y <= -1.0f)
                this.WaveMapOffset1.Y = 0.0f;

        } // End of the update method.

        /// <summary>
        /// Draw the water.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="cameraPosition"></param>
        /// <param name="viewProj"></param>
        public void Draw(GameTime gameTime, Vector3 cameraPosition, Matrix viewProj)
        {
            // Make sure the water is drawn, draw on both sides.
            this.myGraphicsDevice.RasterizerState = RasterizerState.CullNone;

            // Effect parameters that change.
            mEffect.Parameters["WaveMapOffset0"].SetValue(this.WaveMapOffset0);
            mEffect.Parameters["WaveMapOffset1"].SetValue(this.WaveMapOffset1);
            mEffect.Parameters["WorldViewProj"].SetValue(World * viewProj);
            mEffect.Parameters["EyePos"].SetValue(cameraPosition);

            this.myGraphicsDevice.SetVertexBuffer(mVertexBuffer);
            this.myGraphicsDevice.Indices = mIndexBuffer;

            foreach (EffectPass pass in mEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                this.myGraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, mNumVertices, 0, mNumTris);
            }

            // Reset the cullmode to the default cullmode.
            this.myGraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

        } // End of the Draw method

        /// <summary>
        /// Generates a grid of vertices to use for the water plane.
        /// </summary>
        /// <param name="numVertRows">Number of rows. Must be 2^n + 1. Ex. 129, 257, 513.</param>
        /// <param name="numVertCols">Number of columns. Must be 2^n + 1. Ex. 129, 257, 513.</param>
        /// <param name="dx">Cell spacing in the x dimension.</param>
        /// <param name="dz">Cell spacing in the y dimension.</param>
        /// <param name="center">Center of the plane.</param>
        /// <param name="verts">Outputs the constructed vertices for the plane.</param>
        /// <param name="indices">Outpus the constructed triangle indices for the plane.</param>
        private void GenTriGrid(int numVertRows, int numVertCols, float dx, float dz,
                                Vector3 center, out Vector3[] verts, out int[] indices)
        {
            int numVertices = numVertRows * numVertCols;
            int numCellRows = numVertRows - 1;
            int numCellCols = numVertCols - 1;

            int mNumTris = numCellRows * numCellCols * 2;

            float width = (float)numCellCols * dx;
            float depth = (float)numCellRows * dz;

            //===========================================
            // Build vertices.

            // We first build the grid geometry centered about the origin and on
            // the xz-plane, row-by-row and in a top-down fashion.  We then translate
            // the grid vertices so that they are centered about the specified 
            // parameter 'center'.

            verts = new Vector3[numVertices];

            // Offsets to translate grid from quadrant 4 to center of 
            // coordinate system.
            float xOffset = -width * 0.5f;
            float zOffset = depth * 0.5f;

            int k = 0;
            for (float i = 0; i < numVertRows; ++i)
            {
                for (float j = 0; j < numVertCols; ++j)
                {
                    // Negate the depth coordinate to put in quadrant four.  
                    // Then offset to center about coordinate system.
                    verts[k] = new Vector3(0, 0, 0);
                    verts[k].X = j * dx + xOffset;
                    verts[k].Z = -i * dz + zOffset;
                    verts[k].Y = 0.0f;

                    Matrix translation = Matrix.CreateTranslation(center);
                    verts[k] = Vector3.Transform(verts[k], translation);

                    ++k; // Next vertex
                }
            }

            //===========================================
            // Build indices.

            indices = new int[mNumTris * 3];

            // Generate indices for each quad.
            k = 0;
            for (int i = 0; i < numCellRows; ++i)
            {
                for (int j = 0; j < numCellCols; ++j)
                {
                    indices[k] = (i * numVertCols + j);
                    indices[k + 1] = (i * numVertCols + j + 1);
                    indices[k + 2] = ((i + 1) * numVertCols + j);

                    indices[k + 3] = ((i + 1) * numVertCols + j);
                    indices[k + 4] = (i * numVertCols + j + 1);
                    indices[k + 5] = ((i + 1) * numVertCols + j + 1);

                    // next quad
                    k += 6;
                }
            }
        }
    }
}
