//>>> _using
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.Windows;
//<<< _using
using Framefield.Core.OperatorPartTraits;
using Framefield.Core.Rendering;
using Buffer = SharpDX.Direct3D11.Buffer;
using SharpDX.Direct3D;

namespace Framefield.Core.ID9722c7bc_d8d9_4c55_ba68_ee6bfb021cb0
{
    public class Class_QuadScatter : FXSourceCodeFunction, IFXSceneSourceCode, IMeshSupplier
    {
        //>>> _inputids
        private enum InputId
        {
            Code = 0,
            Scene = 1,
            CenterX = 2,
            CenterY = 3,
            CenterZ = 4,
            RangeSize = 5,
            RangeFallOff = 6,
            Push = 7,
            Shrink = 8,
            TranslateX = 9,
            TranslateY = 10,
            TranslateZ = 11,
            ScaleX = 12,
            ScaleY = 13,
            ScaleZ = 14,
            ScaleRandomX = 15,
            ScaleRandomY = 16,
            ScaleRandomZ = 17,
            NoiseTexture = 18,
            NoiseScale = 19,
            ScatterX = 20,
            ScatterY = 21,
            ScatterZ = 22,
            SpinX = 23,
            SpinY = 24,
            SpinZ = 25,
            SpinRandomX = 26,
            SpinRandomY = 27,
            SpinRandomZ = 28
        }
        //<<< _inputids

        public Class_QuadScatter()
        {
            _meshCollector = new MeshCollector(this);
        }

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

        public void AddMeshesTo(ICollection<Mesh> meshes)
        {
            UpdateMesh(new OperatorPartContext(), OperatorPart.Connections);
            if (_mesh != null)
                meshes.Add(_mesh);
        }

        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            try
            {
                UpdateMesh(context, inputs);

                if (_mesh.Vertices != null)
                {
                    // render stream output to screen or whatever target is set
                    context.Renderer.SetupEffect(context);
                    context.Renderer.Render(_mesh, context);
                }
            }
            catch (Exception exception)
            {
                Logger.Error(this, "Load Effect error: {0}", exception.Message);
            }

            return context;
        }

        bool _firstEval = true;
        private void UpdateMesh(OperatorPartContext context, List<OperatorPart> inputs)
        {
            if (_firstEval)
            {
                for (int i = 0; i < NumCodes(); ++i)
                    Compile(i);
                _firstEval = false;
                Changed = true;
            }

            if (!Changed && _mesh.Vertices != null)
                return;

            //>>> _params
            var Code = inputs[(int)InputId.Code].Eval(context).Text;
            var Scene = inputs[(int)InputId.Scene];
            var CenterX = inputs[(int)InputId.CenterX].Eval(context).Value;
            var CenterY = inputs[(int)InputId.CenterY].Eval(context).Value;
            var CenterZ = inputs[(int)InputId.CenterZ].Eval(context).Value;
            var Center = new Vector3(CenterX, CenterY, CenterZ);
            var RangeSize = inputs[(int)InputId.RangeSize].Eval(context).Value;
            var RangeFallOff = inputs[(int)InputId.RangeFallOff].Eval(context).Value;
            var Range = new Vector2(RangeSize, RangeFallOff);
            var Push = inputs[(int)InputId.Push].Eval(context).Value;
            var Shrink = inputs[(int)InputId.Shrink].Eval(context).Value;
            var TranslateX = inputs[(int)InputId.TranslateX].Eval(context).Value;
            var TranslateY = inputs[(int)InputId.TranslateY].Eval(context).Value;
            var TranslateZ = inputs[(int)InputId.TranslateZ].Eval(context).Value;
            var Translate = new Vector3(TranslateX, TranslateY, TranslateZ);
            var ScaleX = inputs[(int)InputId.ScaleX].Eval(context).Value;
            var ScaleY = inputs[(int)InputId.ScaleY].Eval(context).Value;
            var ScaleZ = inputs[(int)InputId.ScaleZ].Eval(context).Value;
            var Scale = new Vector3(ScaleX, ScaleY, ScaleZ);
            var ScaleRandomX = inputs[(int)InputId.ScaleRandomX].Eval(context).Value;
            var ScaleRandomY = inputs[(int)InputId.ScaleRandomY].Eval(context).Value;
            var ScaleRandomZ = inputs[(int)InputId.ScaleRandomZ].Eval(context).Value;
            var ScaleRandom = new Vector3(ScaleRandomX, ScaleRandomY, ScaleRandomZ);
            var NoiseTexture = inputs[(int)InputId.NoiseTexture].Eval(context).Image; // Needs to be checked for null!
            var NoiseScale = inputs[(int)InputId.NoiseScale].Eval(context).Value;
            var ScatterX = inputs[(int)InputId.ScatterX].Eval(context).Value;
            var ScatterY = inputs[(int)InputId.ScatterY].Eval(context).Value;
            var ScatterZ = inputs[(int)InputId.ScatterZ].Eval(context).Value;
            var Scatter = new Vector3(ScatterX, ScatterY, ScatterZ);
            var SpinX = inputs[(int)InputId.SpinX].Eval(context).Value;
            var SpinY = inputs[(int)InputId.SpinY].Eval(context).Value;
            var SpinZ = inputs[(int)InputId.SpinZ].Eval(context).Value;
            var Spin = new Vector3(SpinX, SpinY, SpinZ);
            var SpinRandomX = inputs[(int)InputId.SpinRandomX].Eval(context).Value;
            var SpinRandomY = inputs[(int)InputId.SpinRandomY].Eval(context).Value;
            var SpinRandomZ = inputs[(int)InputId.SpinRandomZ].Eval(context).Value;
            var SpinRandom = new Vector3(SpinRandomX, SpinRandomY, SpinRandomZ);
            //<<< _params

            if (Scene.Func.Changed || _mesh.Vertices == null)
            {
                //Logger.Info(this, "collect meshes");
                _meshCollector.Collect(Scene);
                Scene.Func.Changed = false;
            }

            var inputMesh = _meshCollector.FirstMeshOrDefault;
            if (inputMesh == null)
                return;
                
            if(NoiseTexture == null)
                return;

            _mesh.AttributesSize = inputMesh.AttributesSize;
            _mesh.NumTriangles = inputMesh.NumTriangles;
            _mesh.InputElements = inputMesh.InputElements;
            if (_mesh.Vertices == null || _mesh.Vertices.Description.SizeInBytes != inputMesh.Vertices.Description.SizeInBytes)
            {
                if (_mesh.Vertices != null)
                    _mesh.Vertices.Dispose();

                _mesh.Vertices = new Buffer(D3DDevice.Device, new BufferDescription()
                                                                    {
                                                                        BindFlags = BindFlags.StreamOutput | BindFlags.VertexBuffer,
                                                                        CpuAccessFlags = CpuAccessFlags.None,
                                                                        OptionFlags = ResourceOptionFlags.None,
                                                                        SizeInBytes = _mesh.NumTriangles*inputMesh.AttributesSize*3,
                                                                        Usage = ResourceUsage.Default
                                                                    });
            }

            _effect.GetVariableByName("Center").AsVector().Set(new Vector3(CenterX, CenterY, CenterZ));
            _effect.GetVariableByName("Translate").AsVector().Set(new Vector3(TranslateX, TranslateY, TranslateZ));
            _effect.GetVariableByName("Range").AsVector().Set(new Vector2(RangeSize, RangeFallOff));
            _effect.GetVariableByName("Push").AsScalar().Set(Push);
            _effect.GetVariableByName("Shrink").AsScalar().Set(Shrink);
            _effect.GetVariableByName("Scatter").AsVector().Set(Scatter);
            
            _effect.GetVariableByName("NoiseScale").AsScalar().Set(NoiseScale);
            _effect.GetVariableByName("Scale").AsVector().Set(Scale);
            _effect.GetVariableByName("ScaleRandom").AsVector().Set(ScaleRandom);
            
            _effect.GetVariableByName("Spin").AsVector().Set(Spin);
            _effect.GetVariableByName("SpinRandom").AsVector().Set(SpinRandom);
            
            var noiseTextureView = new ShaderResourceView(context.D3DDevice, NoiseTexture);            
            _effect.GetVariableByName("NoiseTexture").AsShaderResource().SetResource(noiseTextureView);
            
            

            context.D3DDevice.ImmediateContext.StreamOutput.SetTargets(new [] { new StreamOutputBufferBinding(_mesh.Vertices, 0) } );
            context.D3DDevice.ImmediateContext.OutputMerger.DepthStencilState = new DepthStencilState(context.D3DDevice,
                                                                                                        new DepthStencilStateDescription()
                                                                                                            {
                                                                                                                IsDepthEnabled = false,
                                                                                                                IsStencilEnabled = false
                                                                                                            });
            context.D3DDevice.ImmediateContext.InputAssembler.InputLayout = context.InputLayout;
            context.D3DDevice.ImmediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            context.D3DDevice.ImmediateContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(inputMesh.Vertices, inputMesh.AttributesSize, 0));
            var technique = _effect.GetTechniqueByIndex(0);
            technique.GetPassByIndex(0).Apply(context.D3DDevice.ImmediateContext);
            context.D3DDevice.ImmediateContext.Draw(_mesh.NumTriangles*3, 0);
            context.D3DDevice.ImmediateContext.StreamOutput.SetTargets(new [] { new StreamOutputBufferBinding(null, 0) });

            Changed = false;
        }

        private Mesh _mesh = new Mesh();
        private MeshCollector _meshCollector;
    }
}


