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
{
    // A modifiable flow map that uses the GPU to allow someone to paint flow on it.
    class DynamicFlowMap : IDisposable
    {
        public DynamicFlowMap(Game game, SpriteBatch spriteBatch, int width, int height, Vector2 worldTopLeft, Vector2 worldBottomRight)
        {
            Debug.Assert(worldTopLeft.X < worldBottomRight.X);
            Debug.Assert(worldTopLeft.Y < worldBottomRight.Y);

            device = game.GraphicsDevice;
            this.spriteBatch = spriteBatch;
            this.worldTopLeft = worldTopLeft;
            this.worldBottomRight = worldBottomRight;
            worldSize = worldBottomRight - worldTopLeft;

            this.width = width;
            this.height = height;

            // We create two, as we ping-pong between them. We need to do this because we want to do special blending.
            // Ideally we'd use a signed format, but there is no signed format for render targets that supports linear filtering.
            flowMaps[0] = new RenderTarget2D(device, width, height, true, SurfaceFormat.Color, DepthFormat.None);
            flowMaps[1] = new RenderTarget2D(device, width, height, true, SurfaceFormat.Color, DepthFormat.None);

            paintFlowMapEffect = game.Content.Load<Effect>(@"Fx\PaintFlowMapEffect");

            FlowMapTexCoordScaleAndOffset = new Vector4(1f / worldSize.X, 1f / worldSize.Y, -worldTopLeft.X, -worldTopLeft.Y);

            BrushSizeInPixels = 4f;

            game.GraphicsDevice.DeviceReset += new EventHandler<EventArgs>(GraphicsDevice_DeviceReset);
        }

        void GraphicsDevice_DeviceReset(object sender, EventArgs e)
        {
            // We look our flow map when this happens.
            firstTime = true;
        }

        public void Dispose()
        {
            flowMaps[0].Dispose();
            flowMaps[1].Dispose();
        }

        // REVIEW: Handle device reset

        private SpriteBatch spriteBatch;
        private Effect paintFlowMapEffect;
        private GraphicsDevice device;
        private int currentFlowMapIndex = 0;
        private RenderTarget2D[] flowMaps = new RenderTarget2D[2];
        private Vector2 worldTopLeft;
        private Vector2 worldBottomRight;
        private Vector2 worldSize;
        private int width, height;

        public int Width { get { return width; } }
        public int Height { get { return height; } }

        // Constants
        public float BrushSizeInPixels { get; set; }

        private Vector2 WorldPositionToScreenPosition(Vector3 worldPosition)
        {
            Vector2 worldPosition2D = new Vector2(worldPosition.X, worldPosition.Z);
            Vector2 positionFromOrigin = (worldPosition2D - worldTopLeft) / worldSize;
            Vector2 pixelPosition = new Vector2(positionFromOrigin.X, positionFromOrigin.Y);
            return pixelPosition;
        }

        private Vector2? spot;  // In screen space (0,0)-(1,1)
        private Vector2 direction;
        public void PaintOnFlow(Vector3? from, Vector3 to)
        {
            if (from.HasValue)
            {
                Vector3 direction3D = from.Value - to;
                direction = new Vector2(direction3D.X, direction3D.Z);
                float distance = direction.Length();
                if (distance > 0.00001f)
                {
                    direction = Vector2.Normalize(direction);

                    // To start with, let's just draw one at the to.
                    spot = WorldPositionToScreenPosition(to);
                }
                else
                {
                    spot = WorldPositionToScreenPosition(to);
                    direction = Vector2.Zero;
                }
            }
        }

        private bool firstTime = true;

        // Updates the flowmap with the user's painting.
        public void Render()
        {
            if (firstTime)
            {
                // Fill in the flow maps
                Color defaultColor = new Color(128, 128, 255);
                device.SetRenderTarget(flowMaps[0]);
                device.Clear(defaultColor);
                device.SetRenderTarget(flowMaps[1]);
                device.Clear(defaultColor);
                CurrentFlowMap = flowMaps[0]; // Whatever

                firstTime = false;
            }

            if (spot.HasValue)
            {
                device.SetRenderTarget(flowMaps[currentFlowMapIndex]);
                CurrentFlowMap = flowMaps[currentFlowMapIndex];

                currentFlowMapIndex++;
                currentFlowMapIndex %= flowMaps.Length;

                // TODO: Possibly use equation of an ellipse

                paintFlowMapEffect.Parameters["BrushSize"].SetValue(BrushSizeInPixels / width);
                paintFlowMapEffect.Parameters["BrushPosition"].SetValue(spot.Value);
                paintFlowMapEffect.Parameters["BrushDirection"].SetValue(direction);

                // First just draw the previous one.
                spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.LinearWrap, DepthStencilState.None, null, paintFlowMapEffect);
                spriteBatch.Draw(flowMaps[currentFlowMapIndex], Vector2.Zero, Color.White);
                spriteBatch.End();

                spot = null; // Done
            }
        }

        public void DrawFlowMap()
        {
            int width = device.PresentationParameters.BackBufferWidth;
            int height = device.PresentationParameters.BackBufferHeight;
            spriteBatch.Begin();
            spriteBatch.Draw(CurrentFlowMap, new Rectangle(width - 128, height - 128, 128, 128), Color.White);
            spriteBatch.End();
        }

        public Texture2D CurrentFlowMap { get; private set; }

        public Vector4 FlowMapTexCoordScaleAndOffset { get; private set; }
    }
}
