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

namespace WaterTest
{
    // This class is responsible for drawing the water.
    class Water
    {
        public Water(Game game, HeightMap heightMap, Camera camera)
        {
            this.game = game;
            device = game.GraphicsDevice;
            this.camera = camera;
            this.heightMap = heightMap;

            effect = game.Content.Load<Effect>(@"Fx\WaterEffect");
            effect.Parameters["NoiseTexture"].SetValue(game.Content.Load<Texture2D>(@"Textures\NoiseMap"));
            skyTexture = game.Content.Load<Texture2D>(@"Textures\Clouds");
            noSkyTexture = game.Content.Load<Texture2D>(@"Textures\SimpleSky");

            Reset();
        }

        public void Reset()
        {
            currentIndex = -1;
            currentDiffuseIndex = -1;
            ToggleNormalMaps();
            ToggleDiffuseMaps();
            SkyOn = true;
            UseRandomNormalMapOffsets = true;
            NormalTextureFlow = new TextureFlowInfo(
                maxDistortion: 0.4f,
                textureScale: 0.5f,
                cycleSeconds: 3.5f,
                pulseReduction: 0.5f
                );
            DiffuseTextureFlow = new TextureFlowInfo(
                maxDistortion: 0.4f,
                textureScale: 0.5f,
                cycleSeconds: 5.0f,
                pulseReduction: 0.5f
                );
        }

        public TextureFlowInfo NormalTextureFlow { get; private set; }
        public TextureFlowInfo DiffuseTextureFlow { get; private set; }

        private Game game;

        private Texture2D skyTexture;
        private Texture2D noSkyTexture;

        private string[] normalMapNames = new string[]
        {
            "waveB",
            "wave",
            "NoNormal",
        };

        private int currentIndex = -1;
        public void ToggleNormalMaps()
        {
            currentIndex++;
            currentIndex %= normalMapNames.Length;
            effect.Parameters["NormalTexture0"].SetValue(game.Content.Load<Texture2D>("NormalMaps\\" + normalMapNames[currentIndex] + "0"));
            effect.Parameters["NormalTexture1"].SetValue(game.Content.Load<Texture2D>("NormalMaps\\" + normalMapNames[currentIndex] + "1"));
        }

        private string[] diffuseMapNames = new string[]
        {
            null,
            "FoamB",
            "Foam",
            "Debris",
            "DebrisB",
        };

        private int currentDiffuseIndex = -1;
        public void ToggleDiffuseMaps()
        {
            currentDiffuseIndex++;
            currentDiffuseIndex %= diffuseMapNames.Length;
            if (diffuseMapNames[currentDiffuseIndex] == null)
            {
                effect.Parameters["EnableDiffuse"].SetValue(false);
            }
            else
            {
                effect.Parameters["EnableDiffuse"].SetValue(true);
                effect.Parameters["DiffuseTexture0"].SetValue(game.Content.Load<Texture2D>("Diffuse\\" + diffuseMapNames[currentDiffuseIndex] + "0"));
                effect.Parameters["DiffuseTexture1"].SetValue(game.Content.Load<Texture2D>("Diffuse\\" + diffuseMapNames[currentDiffuseIndex] + "1"));
            }
        }

        private HeightMap heightMap;
        private Camera camera;
        private GraphicsDevice device;
        private Effect effect;

        private const float SkyScrollSpeed = 0.01f;
        public Vector3 SunPosition { get; set; }
        public bool SkyOn { get; set; }

        public Visualization Visualization { get; set; }

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

            // Scroll the sky
            skyTextureOffset.X += ellapsedSeconds * SkyScrollSpeed;
            skyTextureOffset.X %= 1f;

            NormalTextureFlow.Update(UseRandomNormalMapOffsets, ellapsedSeconds);
            DiffuseTextureFlow.Update(UseRandomNormalMapOffsets, ellapsedSeconds);

            /*
            // For the normal maps:
            normalCycleProgression += ellapsedSeconds;
            if (UseRandomNormalMapOffsets && !normalHalfWay && (normalCycleProgression > (NormalCycleSeconds * 0.5f)))
            {
                normalHalfWay = true;
                // This means normal map 0 is the one that is invisible
                normal0And1RandomOffsets.X = (float)random.NextDouble();
                normal0And1RandomOffsets.Y = (float)random.NextDouble();
            }
            else if (normalCycleProgression > NormalCycleSeconds)
            {
                if (UseRandomNormalMapOffsets)
                {
                    // This means normal map 1 is the one that is invisible
                    normal0And1RandomOffsets.Z = (float)random.NextDouble();
                    normal0And1RandomOffsets.W = (float)random.NextDouble();
                }

                normalCycleProgression %= NormalCycleSeconds;
                normalHalfWay = false;
            }

            diffuseCycleProgression += ellapsedSeconds;
            if (UseRandomNormalMapOffsets && !diffuseHalfWay && (diffuseCycleProgression > (DiffuseCycleSeconds * 0.5f)))
            {
                diffuseHalfWay = true;
                // This means diffuse map 0 is the one that is invisible
                diffuse0And1RandomOffsets.X = (float)random.NextDouble();
                diffuse0And1RandomOffsets.Y = (float)random.NextDouble();
            }
            else if (diffuseCycleProgression > DiffuseCycleSeconds)
            {
                if (UseRandomNormalMapOffsets)
                {
                    // This means diffuse map 1 is the one that is invisible
                    diffuse0And1RandomOffsets.Z = (float)random.NextDouble();
                    diffuse0And1RandomOffsets.W = (float)random.NextDouble();
                }

                diffuseCycleProgression %= DiffuseCycleSeconds;
                diffuseHalfWay = false;
            }*/
        }

        // These two are incompatible with each other (but we'll let them be set together to see why)
        public bool UsePulseReduction { get; set; }
        private bool useRandomNormalMapOffsets;
        public bool UseRandomNormalMapOffsets
        {
            get { return useRandomNormalMapOffsets; }
            set
            {
                useRandomNormalMapOffsets = value;
                if (!useRandomNormalMapOffsets)
                {
                    NormalTextureFlow._0And1RandomOffsets = Vector4.Zero;
                    DiffuseTextureFlow._0And1RandomOffsets = Vector4.Zero;
                }
            }
        }

        //public float MaxDistortion { get; set; }
        public float MaxDiffuseDistortion { get; set; }
        public Texture2D CurrentFlowMap { get; set; }
        public Vector4 FlowMapTexCoordScaleAndOffset { get; set; }

        private Vector2 skyTextureOffset;

        public void Draw(GameTime gameTime)
        {
            Matrix waterWorldMatrix = Matrix.CreateTranslation(0, 0.00f, 0);

            effect.Parameters["World"].SetValue(waterWorldMatrix);
            effect.Parameters["View"].SetValue(camera.View);
            effect.Parameters["Projection"].SetValue(camera.Projection);
            effect.Parameters["CameraPosition"].SetValue(camera.Position);
            effect.Parameters["DirectionToLight"].SetValue(Vector3.Normalize(SunPosition));

            effect.Parameters["UsePulseReduction"].SetValue(UsePulseReduction);

            effect.Parameters["FlowMapTexture"].SetValue(CurrentFlowMap);
            effect.Parameters["UsingUnsignedFlowMap"].SetValue(CurrentFlowMap.Format == SurfaceFormat.Color);   // There are others, but we're using this one.
            effect.Parameters["FlowMapTexCoordScaleAndOffset"].SetValue(FlowMapTexCoordScaleAndOffset);
            effect.Parameters["SkyTextureOffset"].SetValue(skyTextureOffset);
            effect.Parameters["SkyTexture"].SetValue(SkyOn ? skyTexture : noSkyTexture);

            effect.Parameters["NormalTextureScale"].SetValue(NormalTextureFlow.TextureScale);
            effect.Parameters["MaxNormalDistortion"].SetValue(NormalTextureFlow.MaxDistortion);
            effect.Parameters["NormalPulseReduction"].SetValue(NormalTextureFlow.PulseReduction);
            float normalPhase = NormalTextureFlow.CurrentPhase;
            effect.Parameters["NormalPhase01"].SetValue(new Vector2((normalPhase + 0.5f) % 1, normalPhase));
            effect.Parameters["NormalOffsets0011"].SetValue(NormalTextureFlow._0And1RandomOffsets);

            effect.Parameters["DiffuseTextureScale"].SetValue(DiffuseTextureFlow.TextureScale);
            effect.Parameters["MaxDiffuseDistortion"].SetValue(DiffuseTextureFlow.MaxDistortion);
            effect.Parameters["DiffusePulseReduction"].SetValue(DiffuseTextureFlow.PulseReduction);
            float diffusePhase = DiffuseTextureFlow.CurrentPhase;
            effect.Parameters["DiffusePhase01"].SetValue(new Vector2((diffusePhase + 0.5f) % 1, diffusePhase));// - new Vector2(0.5f));
            effect.Parameters["DiffuseOffsets0011"].SetValue(DiffuseTextureFlow._0And1RandomOffsets);

            heightMap.SetEffectParametersBeforeDraw(effect);

            string techniqueName = "Standard";
            switch (Visualization)
            {
                case WaterTest.Visualization.PulseReductionNoise:
                    techniqueName = "VisualizeNoise";
                    break;
                case WaterTest.Visualization.FlowMap:
                    techniqueName = "VisualizeFlow";
                    break;
            }

            effect.CurrentTechnique = effect.Techniques[techniqueName];
            effect.CurrentTechnique.Passes[0].Apply();

            device.BlendState = BlendState.NonPremultiplied;

            device.SamplerStates[0] = SamplerState.LinearWrap;
            device.SamplerStates[1] = SamplerState.LinearWrap;
            device.SamplerStates[2] = SamplerState.LinearWrap;
            device.SamplerStates[3] = SamplerState.LinearWrap;
            device.SamplerStates[4] = SamplerState.LinearWrap;
            device.SamplerStates[5] = SamplerState.LinearWrap;
            device.SamplerStates[6] = SamplerState.LinearWrap;

            device.SamplerStates[8] = SamplerState.LinearClamp;  // Height map

            heightMap.Draw();
        }
    }

    class TextureFlowInfo
    {
        public TextureFlowInfo(float maxDistortion, float textureScale, float cycleSeconds, float pulseReduction)
        {
            this.MaxDistortion = maxDistortion;
            this.TextureScale = textureScale;
            this.CycleSeconds = cycleSeconds;
            this.PulseReduction = pulseReduction;
            Reset();
        }

        private static Random random = new Random();

        public float MaxDistortion;
        public float TextureScale;
        public Vector4 _0And1RandomOffsets;
        public float PulseReduction;

        public float CurrentPhase
        {
            get { return cycleProgression / CycleSeconds; }
        }

        private float cycleSeconds;
        public float CycleSeconds
        {
            get { return cycleSeconds; }
            set
            {
                if (cycleSeconds != value)
                {
                    cycleSeconds = value;
                    Reset();
                }
            }
        }

        public void Update(bool useRandomMapOffsets, float ellapsedSeconds)
        {
            cycleProgression += ellapsedSeconds;
            if (useRandomMapOffsets && !halfWay && (cycleProgression > (CycleSeconds * 0.5f)))
            {
                halfWay = true;
                // This means normal map 0 is the one that is invisible
                _0And1RandomOffsets.X = (float)random.NextDouble();
                _0And1RandomOffsets.Y = (float)random.NextDouble();
            }
            else if (cycleProgression > CycleSeconds)
            {
                if (useRandomMapOffsets)
                {
                    // This means normal map 1 is the one that is invisible
                    _0And1RandomOffsets.Z = (float)random.NextDouble();
                    _0And1RandomOffsets.W = (float)random.NextDouble();
                }

                cycleProgression %= CycleSeconds;
                halfWay = false;
            }
        }

        private void Reset()
        {
            cycleProgression = 0f;
            halfWay = false;
        }
        private float cycleProgression = 0f;
        private bool halfWay = false;
    }
}
