using System;
using System.Collections.Generic;
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using Framefield.Core.Rendering;
using Buffer = SharpDX.Direct3D11.Buffer;

namespace Framefield.Core.ID5c80aff3_1193_447a_9bbf_5109611728c5
{

    class Particle 
    {
        public Vector3 Position{get;set;}
        public Vector3 Velocity{get;set;}
        public float Size{get;set;}
        public double BirthTime{get;set;}
        public int TextureRow{get;set;}
    }

    public class Class_SimpleParticles : FXSourceCodeFunction, IFXSceneSourceCode
    {
        //>>> _inputids
        private enum InputId
        {
            Code = 0,
            Image = 1,
            Count = 2,
            LifeTime = 3,
            EmitRate = 4,
            EmitPositionX = 5,
            EmitPositionY = 6,
            EmitPositionZ = 7,
            EmitDirectionX = 8,
            EmitDirectionY = 9,
            EmitDirectionZ = 10,
            ScatterSpeed = 11,
            ScatterAngle = 12,
            WindX = 13,
            WindY = 14,
            WindZ = 15,
            Friction = 16,
            ColorR = 17,
            ColorG = 18,
            ColorB = 19,
            ColorA = 20,
            ColorRandomR = 21,
            ColorRandomG = 22,
            ColorRandomB = 23,
            ColorRandomA = 24,
            TextureCellsX = 25,
            TextureCellsY = 26,
            SizeRangeMin = 27,
            SizeRangeMax = 28
        }
        //<<< _inputids


        public Class_SimpleParticles() {
        }

        public override void Dispose() {
            Utilities.DisposeObj(ref _mesh);
            Utilities.DisposeObj(ref _inputLayout);
            base.Dispose();
        }

        bool _firstEval = true;
        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx) {
            if (_firstEval) {
                for (int i = 0; i < NumCodes(); ++i)
                    Compile(i);
                _firstEval = false;
                Changed = true;
            }

            _image = inputs[1].Eval(context).Image;
                
            //>>> _params
            var Code = inputs[(int)InputId.Code].Eval(context).Text;
            var Image = inputs[(int)InputId.Image].Eval(context).Image; // Needs to be checked for null!
            var Count = inputs[(int)InputId.Count].Eval(context).Value;
            var LifeTime = inputs[(int)InputId.LifeTime].Eval(context).Value;
            var EmitRate = inputs[(int)InputId.EmitRate].Eval(context).Value;
            var EmitPositionX = inputs[(int)InputId.EmitPositionX].Eval(context).Value;
            var EmitPositionY = inputs[(int)InputId.EmitPositionY].Eval(context).Value;
            var EmitPositionZ = inputs[(int)InputId.EmitPositionZ].Eval(context).Value;
            var EmitPosition = new Vector3(EmitPositionX, EmitPositionY, EmitPositionZ);
            var EmitDirectionX = inputs[(int)InputId.EmitDirectionX].Eval(context).Value;
            var EmitDirectionY = inputs[(int)InputId.EmitDirectionY].Eval(context).Value;
            var EmitDirectionZ = inputs[(int)InputId.EmitDirectionZ].Eval(context).Value;
            var EmitDirection = new Vector3(EmitDirectionX, EmitDirectionY, EmitDirectionZ);
            var ScatterSpeed = inputs[(int)InputId.ScatterSpeed].Eval(context).Value;
            var ScatterAngle = inputs[(int)InputId.ScatterAngle].Eval(context).Value;
            var WindX = inputs[(int)InputId.WindX].Eval(context).Value;
            var WindY = inputs[(int)InputId.WindY].Eval(context).Value;
            var WindZ = inputs[(int)InputId.WindZ].Eval(context).Value;
            var Wind = new Vector3(WindX, WindY, WindZ);
            var Friction = inputs[(int)InputId.Friction].Eval(context).Value;
            var ColorR = inputs[(int)InputId.ColorR].Eval(context).Value;
            var ColorG = inputs[(int)InputId.ColorG].Eval(context).Value;
            var ColorB = inputs[(int)InputId.ColorB].Eval(context).Value;
            var ColorA = inputs[(int)InputId.ColorA].Eval(context).Value;
            var Color = new Color4(ColorR, ColorG, ColorB, ColorA);
            var ColorRandomR = inputs[(int)InputId.ColorRandomR].Eval(context).Value;
            var ColorRandomG = inputs[(int)InputId.ColorRandomG].Eval(context).Value;
            var ColorRandomB = inputs[(int)InputId.ColorRandomB].Eval(context).Value;
            var ColorRandomA = inputs[(int)InputId.ColorRandomA].Eval(context).Value;
            var ColorRandom = new Color4(ColorRandomR, ColorRandomG, ColorRandomB, ColorRandomA);
            var TextureCellsX = inputs[(int)InputId.TextureCellsX].Eval(context).Value;
            var TextureCellsY = inputs[(int)InputId.TextureCellsY].Eval(context).Value;
            var TextureCells = new Vector2(TextureCellsX, TextureCellsY);
            var SizeRangeMin = inputs[(int)InputId.SizeRangeMin].Eval(context).Value;
            var SizeRangeMax = inputs[(int)InputId.SizeRangeMax].Eval(context).Value;
            var SizeRange = new Vector2(SizeRangeMin, SizeRangeMax);
            //<<< _params
            
            bool resetParticleList = (Count != _count);
            
            _previousEmitPosition = ( _emitPosition != null) ? _emitPosition : EmitPosition;
            _emitPosition = EmitPosition;

            _wind = Wind;
            _emitDirection = EmitDirection;
            _emitRate = EmitRate;
            _scatterAngle = ScatterAngle;
            _scatterSpeed = ScatterSpeed;
            _lifeTime = LifeTime;
            _friction = Friction;
            _textureCells = TextureCells;
            _sizeRange = SizeRange;
            
            _dt = context.Time - _time;
            _time = context.Time;

            if (resetParticleList) 
            {
                _count = (int)Count;
                _count = Math.Max(1, _count);
                _writeIndex=0;
                

                _particles.Clear();
                for( int i = 0; i < Count; ++i ) 
                {
                    _particles.Add( new Particle() { 
                        Position = new Vector3((float) _random.NextDouble(), (float) _random.NextDouble(), (float) _random.NextDouble() ),
                        Velocity = new Vector3((float) _random.NextDouble(), (float) _random.NextDouble(), (float) _random.NextDouble() )

                    } );                    
                }
            }

            UpdateParticles();
            WriteParticles();
            
            Changed = false;

            var D3DDevice = context.D3DDevice;

            try {
                context.D3DDevice.ImmediateContext.ClearState();

                context.ObjectTWorld = Matrix.Identity;
                var matrixVariable = _effect.GetVariableByName("objectToWorldMatrix").AsMatrix();
                matrixVariable.SetMatrix(context.ObjectTWorld);
                matrixVariable = _effect.GetVariableByName("worldToCameraMatrix").AsMatrix();
                matrixVariable.SetMatrix(context.WorldToCamera);
                matrixVariable = _effect.GetVariableByName("cameraToObjectMatrix").AsMatrix();
                var cameraToObject = Matrix.Invert(/*context.ObjectTWorld */ context.WorldToCamera);
                matrixVariable.SetMatrix(cameraToObject);
                matrixVariable = _effect.GetVariableByName("projMatrix").AsMatrix();
                matrixVariable.SetMatrix(context.CameraProjection);
                
                using (var ImageView = new ShaderResourceView(context.D3DDevice, Image))
                {
                    _effect.GetVariableByName("txDiffuse").AsShaderResource().SetResource(ImageView);
                    _effect.GetVariableByName("Count").AsScalar().Set(Count);
                    //_effect.GetVariableByName("RenderTargetSize").AsVector().Set(new Vector2(_usedViewport.Width, _usedViewport.Height));
                    _effect.GetVariableByName("Image").AsShaderResource().SetResource(ImageView);
                    _effect.GetVariableByName("Count").AsScalar().Set(Count);
                    _effect.GetVariableByName("LifeTime").AsScalar().Set(LifeTime);
                    _effect.GetVariableByName("EmitRate").AsScalar().Set(EmitRate);
                    _effect.GetVariableByName("EmitPosition").AsVector().Set(new Vector3(EmitPositionX, EmitPositionY, EmitPositionZ));
                    _effect.GetVariableByName("EmitDirection").AsVector().Set(new Vector3(EmitDirectionX, EmitDirectionY, EmitDirectionZ));
                    _effect.GetVariableByName("ScatterSpeed").AsScalar().Set(ScatterSpeed);
                    _effect.GetVariableByName("ScatterAngle").AsScalar().Set(ScatterAngle);
                    _effect.GetVariableByName("Wind").AsVector().Set(new Vector3(WindX, WindY, WindZ));
                    _effect.GetVariableByName("Friction").AsScalar().Set(Friction);
                    _effect.GetVariableByName("Color").AsVector().Set(new Vector4(ColorR, ColorG, ColorB, ColorA));
                    _effect.GetVariableByName("ColorRandom").AsVector().Set(new Vector4(ColorRandomR, ColorRandomG, ColorRandomB, ColorRandomA));
                    _effect.GetVariableByName("TextureCells").AsVector().Set(new Vector2(TextureCellsX, TextureCellsY));
                    //_effect.GetVariableByName("CenterDistance").AsScalar().Set(CenterDistance);
                    //_effect.GetVariableByName("WaveThickness").AsScalar().Set(WaveThickness);
                    //_effect.GetVariableByName("WaveIntensity").AsScalar().Set(WaveIntensity);
                    /*
                    //>>> _setup
                    using (var ImageView = new ShaderResourceView(context.D3DDevice, Image))
                    {
                        _effect.GetVariableByName("RenderTargetSize").AsVector().Set(new Vector2(_usedViewport.Width, _usedViewport.Height));
                        _effect.GetVariableByName("Image").AsShaderResource().SetResource(ImageView);
                        _effect.GetVariableByName("Count").AsScalar().Set(Count);
                        _effect.GetVariableByName("LifeTime").AsScalar().Set(LifeTime);
                        _effect.GetVariableByName("EmitRate").AsScalar().Set(EmitRate);
                        _effect.GetVariableByName("EmitPosition").AsVector().Set(new Vector3(EmitPositionX, EmitPositionY, EmitPositionZ));
                        _effect.GetVariableByName("EmitDirection").AsVector().Set(new Vector3(EmitDirectionX, EmitDirectionY, EmitDirectionZ));
                        _effect.GetVariableByName("ScatterSpeed").AsScalar().Set(ScatterSpeed);
                        _effect.GetVariableByName("ScatterAngle").AsScalar().Set(ScatterAngle);
                        _effect.GetVariableByName("Wind").AsVector().Set(new Vector3(WindX, WindY, WindZ));
                        _effect.GetVariableByName("Friction").AsScalar().Set(Friction);
                        _effect.GetVariableByName("Color").AsVector().Set(new Vector4(ColorR, ColorG, ColorB, ColorA));
                        _effect.GetVariableByName("ColorRandom").AsVector().Set(new Vector4(ColorRandomR, ColorRandomG, ColorRandomB, ColorRandomA));
                        _effect.GetVariableByName("TextureCells").AsVector().Set(new Vector2(TextureCellsX, TextureCellsY));
                        _effect.GetVariableByName("SizeRange").AsVector().Set(new Vector2(SizeRangeMin, SizeRangeMax));
                    //<<< _setup
                    */

                    if (context.DepthStencilView != null)
                        context.D3DDevice.ImmediateContext.OutputMerger.SetTargets(context.DepthStencilView, context.RenderTargetView);
                    else
                        context.D3DDevice.ImmediateContext.OutputMerger.SetTargets(context.RenderTargetView);
    
                    if (context.BlendState != null) {
                        context.D3DDevice.ImmediateContext.OutputMerger.BlendState = context.BlendState;
                    }
    
                    if (context.DepthStencilState != null) {
                        context.D3DDevice.ImmediateContext.OutputMerger.DepthStencilState = context.DepthStencilState;
                    }
    
                    if (context.RasterizerState != null) {
                        context.D3DDevice.ImmediateContext.Rasterizer.State = context.RasterizerState;
                    }

                    context.D3DDevice.ImmediateContext.Rasterizer.SetViewports(new [] { context.Viewport });
                    context.D3DDevice.ImmediateContext.InputAssembler.InputLayout = _inputLayout;
                    context.D3DDevice.ImmediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
                    context.D3DDevice.ImmediateContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(_mesh.Vertices, _mesh.AttributesSize, 0));
    
                    var technique = _effect.GetTechniqueByIndex(0);
                    for (int i2 = 0; i2 < technique.Description.PassCount; ++i2) {
                        technique.GetPassByIndex(i2).Apply(context.D3DDevice.ImmediateContext);
                        context.D3DDevice.ImmediateContext.Draw(_mesh.NumTriangles * 3, 0);
                    }
                }
            }
            catch (Exception exception) {
                Logger.Error(this,"Load Effect error: {0}", exception.Message);
            }

            return context;
        }

        //>>> Update
        private void UpdateParticles() 
        {
            int particleEmitCountForFrame = (int)(_dt * _emitRate * 60);
            // Emit new
            for( var i =0; i< particleEmitCountForFrame; ++i) {
                var interpolatedPos = _emitPosition + (_previousEmitPosition - _emitPosition) * (float)i/particleEmitCountForFrame;
                _particles[(_writeIndex++) % _count] = EmitNewParticle( interpolatedPos ); 
            }
        
            // Update
            foreach( var p in _particles ) 
            {    
                p.Position +=  p.Velocity * (float)_dt;
                p.Velocity = (1-_friction * (float)_dt ) * p.Velocity +  _wind * (float)_dt;
            } 
            //Logger.Info(this,"Count {0}", _particles.Count);
        }
        
        private Particle EmitNewParticle( Vector3 emitPos) 
        {
            var v= _emitDirection * ( 1-  (float)(_random.NextDouble() * _scatterSpeed));
            
            // Rotate Vector here... 
            float angle = (float)((_random.NextDouble() - 0.5 ) * _scatterAngle / 180 * Math.PI);
            v= new Vector3( 
            (float)(v.X * Math.Cos( angle) - v.Y * Math.Sin( angle )), 
            (float)(v.X * Math.Sin( angle) + v.Y * Math.Cos( angle )), 
            v.Z);
            
            var p= new Particle() { 
                Position=  emitPos,
                Velocity = v,
                TextureRow = (int)(_random.NextDouble() * _textureCells.Y),
                Size = (float) (_random.NextDouble() * (_sizeRange.Y-_sizeRange.X) + _sizeRange.X),
                BirthTime = _time
            };
            return p;    
        }
        
        //<<< Update

        private void WriteParticles() 
        {
            //Dispose();
            var inputElements = new InputElement[] {
                            new InputElement("POSITION", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 0, 0),
                            new InputElement("OFFSET", 0, SharpDX.DXGI.Format.R32G32B32_Float, 16, 0),
//                            new InputElement("OFFSET", 0, SharpDX.DXGI.Format.R32G32_Float, 16, 0),
//                            new InputElement("ROW", 0, SharpDX.DXGI.Format.R32_UInt, 24, 0)
                        };

            var attributesSize = 28;
            int streamSize = 2 * 3 * attributesSize * _count;
            using (var vertexStream = new DataStream(streamSize, true, true))
            {

                Random _random = new Random(_seed);
                
                float gridSizeHalf = _gridSize/2.0f;
    
                //for (int particleIdx = 0; particleIdx < _count; ++particleIdx) {
                foreach( var p in _particles)
                {
                /*
                    float noise1 = (float) _random.NextDouble();
                    float noise2 = (float) _random.NextDouble();
                    float noise3 = (float) _random.NextDouble();
    
                    var pos = new Vector4(-gridSizeHalf + _gridSize * noise1,
                                          -gridSizeHalf + _gridSize * noise2,
                                          -gridSizeHalf + _gridSize * noise3,
                                          1.0f);
                    var size = _particleSizeRange.X + (_particleSizeRange.Y - _particleSizeRange.X) * ((float) _random.NextDouble());
                    var halfSize = size/2.0f;
                    */
                    
                    var pos = new Vector4(p.Position, 1.0f);
                    float age = Math.Min( 1, (float)((_time - p.BirthTime) / _lifeTime));
                    
                    float halfSize = 0.5f * p.Size * (1-age);
                    if( halfSize < 0) 
                        continue;
    
                    
                    float row = p.TextureRow; // (int) (((float) _random.NextDouble()) * (_textureCells.X - 1.0f));                
                    //Logger.Info(this,"row: {0}", row);
    
                    // tri 1 vert 1
                    vertexStream.Write(pos);
                    vertexStream.Write(new Vector3(halfSize, halfSize, row));
    //                vertexStream.Write(row);
    
                    // tri 1 vert 2
                    vertexStream.Write(pos);
                    vertexStream.Write(new Vector3(halfSize, -halfSize, row));
    //                vertexStream.Write(row);
    
                    // tri 1 vert 3
                    vertexStream.Write(pos);
                    vertexStream.Write(new Vector3(-halfSize, -halfSize, row));
    //                vertexStream.Write(row);
    
                    // tri 2 vert 1
                    vertexStream.Write(pos);
                    vertexStream.Write(new Vector3(-halfSize, -halfSize, row));
    //                vertexStream.Write(row);
    
                    // tri 2 vert 2
                    vertexStream.Write(pos);
                    vertexStream.Write(new Vector3(-halfSize, halfSize, row));
    //                vertexStream.Write(row);
    
                    // tri 2 vert 3
                    vertexStream.Write(pos);
                    vertexStream.Write(new Vector3(halfSize, halfSize, row));
    //                vertexStream.Write(row);
    
                }
    
                vertexStream.Position = 0;
    
                var vertices = new Buffer(D3DDevice.Device, vertexStream, new BufferDescription() {
                    BindFlags = BindFlags.VertexBuffer,
                    CpuAccessFlags = CpuAccessFlags.None,
                    OptionFlags = ResourceOptionFlags.None,
                    SizeInBytes = streamSize,
                    Usage = ResourceUsage.Default
                });
    
                Utilities.DisposeObj(ref _mesh);
                Utilities.DisposeObj(ref _inputLayout);
                _mesh = new Mesh() { InputElements = inputElements, Vertices = vertices, NumTriangles = _count*2, AttributesSize = attributesSize };
                if (_effect != null && Core.D3DDevice.Device != null) {
                    var technique = _effect.GetTechniqueByIndex(0);
                    var pass = technique.GetPassByIndex(0);
                    _inputLayout = new InputLayout(Core.D3DDevice.Device, pass.Description.Signature, inputElements);
                }
            }
        }

        List<Particle> _particles = new List<Particle>();

        InputLayout _inputLayout = null;
        Texture2D _image = null;
        Mesh _mesh = null;
        int _count;
        int _writeIndex=0;
        int _seed;
        float _gridSize;
        Vector2 _textureCells;
        Vector2 _sizeRange;
        
        Vector3 _wind;
        Vector3 _emitDirection;
        
        Vector3 _previousEmitPosition;
        Vector3 _emitPosition;
        float _emitRate;
        float _scatterAngle;
        float  _scatterSpeed;
        float  _lifeTime;
        float _friction;
        double _dt;
        double _time;

        Random _random = new Random(232);

    }
}


