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

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;

using EVE.EVEShaders;

namespace EVE.EVEGpgpu
{
    public sealed class EVENavierStokes
    {
        RenderTarget2D velocityArray;
        RenderTarget2D pressureArray;
        RenderTarget2D divergenceArray;
        RenderTarget2D tempArray;
        RenderTarget2D obstacleArray;
        RenderTarget2D debugArray;

        Texture2D obstacleTex;
        Texture2D heroTex;

        EVEGpKernel computeKernel;

        int cellNo;
        float Texel;
        double dTime;
        Vector2 lastMPos;

        GraphicsDevice currentProcessor;
        ContentManager cLoader;

        EVEEffect spriteVel;
        SpriteBatch spriteBatch;

        private static readonly EVENavierStokes instance = new EVENavierStokes();

        public static EVENavierStokes Instance
        {
           get { return instance; }
        }

        public void Initialize(GraphicsDevice myDevice, ContentManager myLoader)
        {
            currentProcessor = myDevice;
            cLoader = myLoader;
            spriteBatch = new SpriteBatch(currentProcessor);

            EVEGpKernel baseKernel;
            baseKernel = new EVEGpKernel(@"Content/Shaders/GPGPU_TEST");
            EVEShaderManager.AddKernel("GPGPU_TEST", baseKernel);

            baseKernel = new EVEGpKernel(@"Content/Shaders/GPGPU_INIT");
            EVEShaderManager.AddKernel("GPGPU_INIT", baseKernel);

            baseKernel = new EVEGpKernel(@"Content/Shaders/DEBUG_DRAW");
            EVEShaderManager.AddKernel("DEBUG_DRAW", baseKernel);

            baseKernel = new EVEGpKernel(@"Content/Shaders/GPGPU/GPGPU_NavierStokes");
            EVEShaderManager.AddKernel("GPGPU_NavierStokes", baseKernel);

            spriteVel = new EVEEffect(@"Content/Shaders/GPGPU/SpriteVelocity");
            EVEShaderManager.AddEffect("SpriteVelocity", spriteVel);

            cellNo = 256;

            Texel = (1.0f/(float)(cellNo));

            velocityArray = new RenderTarget2D(myDevice, cellNo, cellNo, 1, SurfaceFormat.HalfVector2);
            pressureArray = new RenderTarget2D(myDevice, cellNo, cellNo, 1, SurfaceFormat.HalfVector2);
            divergenceArray = new RenderTarget2D(myDevice, cellNo, cellNo, 1, SurfaceFormat.HalfVector2);
            tempArray = new RenderTarget2D(myDevice, cellNo, cellNo, 1, SurfaceFormat.HalfVector2);
            debugArray = new RenderTarget2D(myDevice, cellNo, cellNo, 1, SurfaceFormat.HalfVector2);
            obstacleArray = new RenderTarget2D(myDevice, cellNo, cellNo, 1, SurfaceFormat.HalfVector2);

            dTime = 0;
        }

        public void PerformArrayInit(Vector4 initVal)
        {
            lastMPos = new Vector2(0.0f,0.0f);

            computeKernel = EVEShaderManager.GetShader("GPGPU_NavierStokes") as EVEGpKernel;
            computeKernel.SetTechnique("Init");
            computeKernel.myEffect.Parameters["initValue"].SetValue(new Vector4(1,0,0,0));
            computeKernel.CalculateSingleKernel(currentProcessor, "Init", tempArray, velocityArray);

            computeKernel.myEffect.Parameters["initValue"].SetValue(new Vector4(0, 0, 0, 0));
            computeKernel.CalculateSingleKernel(currentProcessor, "Init", tempArray, pressureArray);

            obstacleTex = cLoader.Load<Texture2D>(@"Content/Textures/solid") as Texture2D;
            heroTex = cLoader.Load<Texture2D>(@"Content/Textures/hero") as Texture2D;

            computeKernel.PerformCopy(currentProcessor, obstacleTex, obstacleArray);            
        }
        
        public void PerformCalculation()
        {
            computeKernel.myEffect.Parameters["fullTexel"].SetValue(Texel);
            computeKernel.myEffect.Parameters["halfTexel"].SetValue((float)(0.5f*Texel));

            //computeKernel.PerformDebugDraw(currentProcessor, "DrawVelocity", velocityArray);

            //
            //XNA Navier Stokes Integration Prototype
            //
            //General info
            //
            //Prototype is using backcopying with tempArray to initial texture for texture updates
            //
            //Performance version should use : V1, V2, P1, P2 , O - textures
            //

            //
            // Advection
            //
            // Performance: V1 -> V2
            //
            computeKernel.SetTechnique("AdvectVel");
            computeKernel.myEffect.Parameters["dTime"].SetValue((float)dTime);                               
            computeKernel.CalculateSingleKernel(currentProcessor,"AdvectVel", velocityArray, tempArray, velocityArray);

            computeKernel.PerformCopy(currentProcessor, velocityArray, debugArray);
            
            //
            //Divergence
            //
            // Performance V2 -> V1
            //
            computeKernel.SetTechnique("Divergence");
            computeKernel.CalculateSingleKernel(currentProcessor, "Divergence", velocityArray, velocityArray, obstacleArray, tempArray, divergenceArray);

            //computeKernel.PerformCopy(currentProcessor, divergenceArray, debugArray);
            
            //
            //Jacobi Iteration
            //
            // Performance V1->P1->O->P2
            // Variable num of steps : >1 == PingPong
            //

            computeKernel.SetTechnique("JacobiIteration");
            for (int i = 0; i < 20; i++)
            {
                computeKernel.CalculateSingleKernel(currentProcessor,"JacobiIteration", divergenceArray, pressureArray, obstacleArray, tempArray, pressureArray);
            }

            //computeKernel.PerformCopy(currentProcessor, pressureArray, debugArray);
            
            //
            //Projection
            //
            // Performance V2-P2-O->V1
            //

            computeKernel.SetTechnique("Project");
            computeKernel.CalculateSingleKernel(currentProcessor,"Project", velocityArray, pressureArray, obstacleArray, tempArray, velocityArray);

            //computeKernel.PerformCopy(currentProcessor, velocityArray, debugArray);
            
            
            //
            //Add external force
            //
            //Render sprites with velocity representation of units to Obstacle Array
            //Static object get epsilon > 0 value for velocity
            //Code below for XNA prototype only
            //
            float h = currentProcessor.PresentationParameters.BackBufferHeight;
            float w = currentProcessor.PresentationParameters.BackBufferWidth;

            MouseState ms = Mouse.GetState();
            Vector2 aMPos = new Vector2((float)(ms.X/w), (float)(ms.Y/h));
            Vector2 velForce = -(lastMPos - aMPos);

            if (ms.LeftButton == ButtonState.Pressed)
            {
                computeKernel.myEffect.Parameters["velCoef"].SetValue(1f);
                spriteVel.Effect.Parameters["velCoef"].SetValue(1f);
            }
            else
            {
                computeKernel.myEffect.Parameters["velCoef"].SetValue(0f);
                spriteVel.Effect.Parameters["velCoef"].SetValue(0f);
            }

            computeKernel.myEffect.Parameters["velForce"].SetValue(velForce);
            computeKernel.myEffect.Parameters["Point"].SetValue(aMPos);

  
            RenderTarget2D oldRT = currentProcessor.GetRenderTarget(0) as RenderTarget2D;
            currentProcessor.SetRenderTarget(0, obstacleArray);

            currentProcessor.Clear(Color.Black);

            spriteVel.Effect.Parameters["velForce"].SetValue(velForce);


            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
            spriteBatch.Draw(obstacleTex, new Rectangle(0, 0, cellNo, cellNo), Color.White);
            spriteBatch.End();

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
            spriteVel.Effect.Begin();
            spriteVel.Effect.CurrentTechnique.Passes[0].Begin();
            spriteBatch.Draw(heroTex, aMPos * cellNo, Color.White);
            spriteVel.Effect.CurrentTechnique.Passes[0].End();
            spriteVel.Effect.End();
            spriteBatch.End();            

            currentProcessor.RenderState.DepthBufferEnable = true;
            currentProcessor.RenderState.AlphaBlendEnable = false;
            currentProcessor.RenderState.AlphaTestEnable = false;

            currentProcessor.SetRenderTarget(0, null);
         
            //computeKernel.PerformCopy(currentProcessor, tempArray, debugArray);         
                             
            lastMPos = aMPos;
            
        }

        public void DrawToBuffer(int rtIndex, RenderTarget2D renderTarget)
        {
            currentProcessor.SetRenderTarget(rtIndex, renderTarget);
            computeKernel.PerformDebugDraw(currentProcessor, "DrawNormal", pressureArray);
        }

        public void PerformFullStep(GameTime gameTime)
        {
            dTime = 0.0001*gameTime.ElapsedGameTime.Milliseconds;
            PerformCalculation();
        }
    }
}
