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.IDbb8be00a_3a9e_4538_8816_a27803d246dc
{
    public class Class_DustParticles : FXSourceCodeFunction, IFXSceneSourceCode
    {
        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;
                
            if (Changed) {
                var count = (int) inputs[2].Eval(context).Value;
                var seed = (int) inputs[3].Eval(context).Value;
                var gridSize = inputs[4].Eval(context).Value;
                var particleSizeRange = Utilities.EvaluateVector2(context, inputs, 5);
                _offset = Utilities.EvaluateVector3(context, inputs, 7);
                _focus = Utilities.EvaluateVector2(context, inputs, 10);
                var textureCells = Utilities.EvaluateVector2(context, inputs, 12);
                _waveOffset = Utilities.EvaluateVector3(context, inputs, 14);
                _waveFrequency = Utilities.EvaluateVector3(context, inputs, 17);
                _waveAmplitude = Utilities.EvaluateVector3(context, inputs, 20);
                _waveCenter = Utilities.EvaluateVector3(context, inputs, 23);
                _centerDistance = inputs[26].Eval(context).Value;
                _waveThickness = inputs[27].Eval(context).Value;
                _waveIntensity = inputs[28].Eval(context).Value;
                
                bool rebuildParticles = (count != _count) ||
                                        (seed != _seed) ||
                                        (gridSize != _gridSize) ||
                                        (particleSizeRange != _particleSizeRange) ||
                                        (textureCells != _textureCells);
                _count = count;
                _seed = seed;
                _gridSize = gridSize;
                _particleSizeRange = particleSizeRange;
                _textureCells = textureCells;
                
                if (rebuildParticles)
                    CreateParticles();
                
                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);
                //Logger.Info(this,"campos: {0}", cameraToObject.get_Rows(3));
                matrixVariable.SetMatrix(cameraToObject);
                matrixVariable = _effect.GetVariableByName("projMatrix").AsMatrix();
                matrixVariable.SetMatrix(context.CameraProjection);

                using (var imageResourceView = new ShaderResourceView(D3DDevice, _image)) {
                    var textureVariable = _effect.GetVariableByName("txDiffuse").AsShaderResource();
                    textureVariable.SetResource(imageResourceView);
    
                    var floatVariable = _effect.GetVariableByName("gridSize").AsScalar();
                    floatVariable.Set(_gridSize);
    
                    var vectorVariable = _effect.GetVariableByName("offset").AsVector();
                    vectorVariable.Set(_offset);
    
                    floatVariable = _effect.GetVariableByName("focusDistance").AsScalar();
                    floatVariable.Set(_focus.X);
    
                    floatVariable = _effect.GetVariableByName("focusRange").AsScalar();
                    floatVariable.Set(_focus.Y);
    
                    vectorVariable = _effect.GetVariableByName("textureCells").AsVector();
                    vectorVariable.Set(_textureCells);
    
                    vectorVariable = _effect.GetVariableByName("waveOffset").AsVector();
                    vectorVariable.Set(_waveOffset);
    
                    vectorVariable = _effect.GetVariableByName("waveFrequency").AsVector();
                    vectorVariable.Set(_waveFrequency);
    
                    vectorVariable = _effect.GetVariableByName("waveAmplitude").AsVector();
                    vectorVariable.Set(_waveAmplitude);
    
                    vectorVariable = _effect.GetVariableByName("waveCenter").AsVector();
                    vectorVariable.Set(_waveCenter);
    
                    floatVariable = _effect.GetVariableByName("waveCenterDist").AsScalar();
                    floatVariable.Set(_centerDistance);
    
                    floatVariable = _effect.GetVariableByName("waveThickness").AsScalar();
                    floatVariable.Set(_waveThickness);
    
                    floatVariable = _effect.GetVariableByName("waveIntensity").AsScalar();
                    floatVariable.Set(_waveIntensity);
    
                    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;
        }

//         void updateSeed(ref int seed) {
//           int n = seed*137;
//           seed = (n << 13) ^ n;
//         }

        private void CreateParticles() {
            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;
            var vertexStream = new DataStream(streamSize, true, true);

            Random _random = new Random(_seed);
            float gridSizeHalf = _gridSize/2.0f;

            for (int particleIdx = 0; particleIdx < _count; ++particleIdx) {
                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;
                float row = (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);
            }
        }

        InputLayout _inputLayout = null;
        Texture2D _image = null;
        Mesh _mesh = null;
        int _count;
        int _seed;
        float _gridSize;
        Vector2 _particleSizeRange;
        Vector3 _offset;
        Vector2 _focus;
        Vector2 _textureCells;
        Vector3 _waveOffset;
        Vector3 _waveFrequency;
        Vector3 _waveAmplitude;
        Vector3 _waveCenter;
        float _centerDistance;
        float _waveThickness;
        float _waveIntensity;
    }
}


