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.ID2442812b_55f1_4a23_a396_dac74e9ce0d6
{
    public class Class_DustParticles : FXSourceCodeFunction, IFXSceneSourceCode
    {
        //>>> _inputids
        private enum InputId
        {
            Code = 0,
            Image = 1,
            Count = 2,
            Seed = 3,
            BoxSize = 4,
            SizeRangeX = 5,
            SizeRangeY = 6,
            OffsetX = 7,
            OffsetY = 8,
            OffsetZ = 9,
            FocusDistance = 10,
            FocusRange = 11,
            TextureCellsX = 12,
            TextureCellsY = 13,
            DensityWaveLengthX = 14,
            DensityWaveLengthY = 15,
            DensityWaveLengthZ = 16,
            DensityWaveStrengthX = 17,
            DensityWaveStrengthY = 18,
            DensityWaveStrengthZ = 19,
            WaveLengthX = 20,
            WaveLengthY = 21,
            WaveLengthZ = 22,
            WaveStrengthX = 23,
            WaveStrengthY = 24,
            WaveStrengthZ = 25,
            WiggleStrength = 26,
            WiggleOffset = 27,
            WiggleNoise = 28
        }
        //<<< _inputids


        public Class_DustParticles() {
        }

        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; if (Image == null) return context;
            var Count = inputs[(int)InputId.Count].Eval(context).Value;
            var Seed = inputs[(int)InputId.Seed].Eval(context).Value;
            var BoxSize = inputs[(int)InputId.BoxSize].Eval(context).Value;
            var SizeRangeX = inputs[(int)InputId.SizeRangeX].Eval(context).Value;
            var SizeRangeY = inputs[(int)InputId.SizeRangeY].Eval(context).Value;
            var SizeRange = new Vector2(SizeRangeX, SizeRangeY);
            var OffsetX = inputs[(int)InputId.OffsetX].Eval(context).Value;
            var OffsetY = inputs[(int)InputId.OffsetY].Eval(context).Value;
            var OffsetZ = inputs[(int)InputId.OffsetZ].Eval(context).Value;
            var Offset = new Vector3(OffsetX, OffsetY, OffsetZ);
            var FocusDistance = inputs[(int)InputId.FocusDistance].Eval(context).Value;
            var FocusRange = inputs[(int)InputId.FocusRange].Eval(context).Value;
            var Focus = new Vector2(FocusDistance, FocusRange);
            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 DensityWaveLengthX = inputs[(int)InputId.DensityWaveLengthX].Eval(context).Value;
            var DensityWaveLengthY = inputs[(int)InputId.DensityWaveLengthY].Eval(context).Value;
            var DensityWaveLengthZ = inputs[(int)InputId.DensityWaveLengthZ].Eval(context).Value;
            var DensityWaveLength = new Vector3(DensityWaveLengthX, DensityWaveLengthY, DensityWaveLengthZ);
            var DensityWaveStrengthX = inputs[(int)InputId.DensityWaveStrengthX].Eval(context).Value;
            var DensityWaveStrengthY = inputs[(int)InputId.DensityWaveStrengthY].Eval(context).Value;
            var DensityWaveStrengthZ = inputs[(int)InputId.DensityWaveStrengthZ].Eval(context).Value;
            var DensityWaveStrength = new Vector3(DensityWaveStrengthX, DensityWaveStrengthY, DensityWaveStrengthZ);
            var WaveLengthX = inputs[(int)InputId.WaveLengthX].Eval(context).Value;
            var WaveLengthY = inputs[(int)InputId.WaveLengthY].Eval(context).Value;
            var WaveLengthZ = inputs[(int)InputId.WaveLengthZ].Eval(context).Value;
            var WaveLength = new Vector3(WaveLengthX, WaveLengthY, WaveLengthZ);
            var WaveStrengthX = inputs[(int)InputId.WaveStrengthX].Eval(context).Value;
            var WaveStrengthY = inputs[(int)InputId.WaveStrengthY].Eval(context).Value;
            var WaveStrengthZ = inputs[(int)InputId.WaveStrengthZ].Eval(context).Value;
            var WaveStrength = new Vector3(WaveStrengthX, WaveStrengthY, WaveStrengthZ);
            var WiggleStrength = inputs[(int)InputId.WiggleStrength].Eval(context).Value;
            var WiggleOffset = inputs[(int)InputId.WiggleOffset].Eval(context).Value;
            var WiggleNoise = inputs[(int)InputId.WiggleNoise].Eval(context).Value;
            //<<< _params
            
            bool rebuildParticles = (Count != _count) ||
                                    (Seed != _seed) ||
                                    (BoxSize != _gridSize) ||
                                    (SizeRange != _particleSizeRange) ||
                                    (TextureCells != _textureCells);
            _count = (int)Count;
            _seed = (int)Seed;
            _gridSize = BoxSize;
            _particleSizeRange = SizeRange;
            _textureCells = TextureCells;

            _count = Math.Max(1, _count);

            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);
                matrixVariable.SetMatrix(cameraToObject);
                matrixVariable = _effect.GetVariableByName("projMatrix").AsMatrix();
                matrixVariable.SetMatrix(context.CameraProjection);
                
                //>>> setup (Code NOT auto generated)
                using (var ImageView = new ShaderResourceView(context.D3DDevice, Image))
                {
                    _effect.GetVariableByName("txDiffuse").AsShaderResource().SetResource(ImageView);
                    _effect.GetVariableByName("Count").AsScalar().Set(Count);
                    _effect.GetVariableByName("Seed").AsScalar().Set(Seed);
                    _effect.GetVariableByName("GridSize").AsScalar().Set(BoxSize);
                    _effect.GetVariableByName("SizeRange").AsVector().Set(new Vector2(SizeRangeX, SizeRangeY));
                    _effect.GetVariableByName("Offset").AsVector().Set(new Vector3(OffsetX, OffsetY, OffsetZ));
                    _effect.GetVariableByName("Focus").AsVector().Set(new Vector2(FocusDistance, FocusRange));
                    _effect.GetVariableByName("TextureCells").AsVector().Set(new Vector2(TextureCellsX, TextureCellsY));
                    _effect.GetVariableByName("DensityWaveLength").AsVector().Set(new Vector3(DensityWaveLengthX, DensityWaveLengthY, DensityWaveLengthZ));
                    _effect.GetVariableByName("DensityWaveStrength").AsVector().Set(new Vector3(DensityWaveStrengthX, DensityWaveStrengthY, DensityWaveStrengthZ));
                    _effect.GetVariableByName("WaveLength").AsVector().Set(new Vector3(WaveLengthX, WaveLengthY, WaveLengthZ));
                    _effect.GetVariableByName("WaveStrength").AsVector().Set(new Vector3(WaveStrengthX, WaveStrengthY, WaveStrengthZ));
                    _effect.GetVariableByName("WiggleStrength").AsScalar().Set(WiggleStrength);
                    _effect.GetVariableByName("WiggleOffset").AsScalar().Set( WiggleOffset );
                    _effect.GetVariableByName("WiggleNoise").AsScalar().Set( WiggleNoise );
                    //_effect.GetVariableByName("CenterDistance").AsScalar().Set(CenterDistance);
                    //_effect.GetVariableByName("WaveThickness").AsScalar().Set(WaveThickness);
                    //_effect.GetVariableByName("WaveIntensity").AsScalar().Set(WaveIntensity);
                    //<<< setup (Code NOT auto generated)

                    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(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;
        }

        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;
            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)
                {
                    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;
        Vector2 _textureCells;
    }
}


