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

namespace Framefield.Core.ID30dfb013_0077_4eac_8244_09ad98ee2f34
{
    public class Class_RibbonEmitter : FXSourceCodeFunction
    {

        #region Setup and Renderer
        public class Renderer : BaseRenderer
        {
            public override void SetupEffect(OperatorPartContext context)
            {
                base.SetupEffect(context);
                try
                {
                    SetupMaterialConstBuffer(context);
                    SetupFogSettingsConstBuffer(context);
                    SetupPointLightsConstBuffer(context);
                }
                catch (Exception e)
                {
                    Logger.Error(ParentFunc, "Error building constant buffer: {0} - Source: {1}", e.Message, e.Source);
                }
            }
            public OperatorPart.Function ParentFunc { get; set; }
        }

        public Class_RibbonEmitter()
        {
            _renderer = new Renderer() { ParentFunc = this };
        }

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

        private void CompileCodePartsOnFirstEvaluation()
        {
            if (!_firstEval)
                return;

            for (var i = 0; i < NumCodes(); ++i)
            {
                Compile(i);
            }
            _firstEval = false;
            Changed = true;
        }
        #endregion

        #region Parameters and inputs
        //>>> _inputids
        private enum InputId
        {
            Code = 0,
            Geometry = 1,
            MaxCount = 2,
            EmitCount = 3,
            LifeTime = 4,
            ResetTrigger = 5,
            EmitFrom = 6,
            EmitterSizeWidth = 7,
            EmitterSizeHeight = 8,
            EmitterSizeDepth = 9,
            LimitPositions = 10,
            ScatterX = 11,
            ScatterY = 12,
            ScatterZ = 13,
            EmitVelocity = 14,
            EmitAngleAValue = 15,
            EmitAngleAScatter = 16,
            EmitAngleBValue = 17,
            EmitAngleBScatter = 18,
            SizeValue = 19,
            SizeRandom = 20,
            StretchX = 21,
            StretchY = 22,
            StrechByMotion = 23,
            ShrinkOverTimeAtBeginning = 24,
            ShrinkOverTimeAtEnd = 25,
            Orientation = 26,
            VelocityFriction = 27,
            GravityX = 28,
            GravityY = 29,
            GravityZ = 30,
            TurbulenceAmount = 31,
            TurbulenceTime = 32,
            TurbulenceVariationScale = 33,
            TextureCellsX = 34,
            TextureCellsY = 35,
            ColorStartR = 36,
            ColorStartG = 37,
            ColorStartB = 38,
            ColorStartA = 39,
            ColorVariationHue = 40,
            ColorVariationSatuation = 41,
            ColorVariationBrightness = 42,
            ColorVariationAlpha = 43,
            ColorEndR = 44,
            ColorEndG = 45,
            ColorEndB = 46,
            ColorEndA = 47,
            FadeWithAgeAtBeginning = 48,
            FadeWithAgeAtEnd = 49,
            FocusCenter = 50,
            FocusRange = 51,
            FadeTooCloseDistance = 52,
            FadeTooCloseSmooth = 53,
            RandomSeed = 54,
            EmitCenterX = 55,
            EmitCenterY = 56,
            EmitCenterZ = 57,
            StretchZ = 58,
            NoiseTexture = 59,
            ParametersAffect = 60,
            TurbulenceVariation = 61,
            Length = 62,
            UpVectorX = 63,
            UpVectorY = 64,
            UpVectorZ = 65,
            TurbulenceFadeIn = 66,
            EmitDirectionX = 67,
            EmitDirectionY = 68,
            EmitDirectionZ = 69,
            TurbulenceSplit = 70
        }
        //<<< _inputids

        enum EmitterShapeType
        {
            Plane = 0,
            Sphere,
            Ring
        }

        private OperatorPart _geometry;
        private EmitterShapeType _emitterShape;
        private float _emitCount;
        private int _maxCount;
        private float _lifeTime;
        private bool _resetTrigger;
        private Vector3 _emitterSize;
        private float _limitPositions;
        private Vector3 _scatter;
        private float _emitVelocity;
        private Vector2 _emitAngleA;
        private Vector2 _emitAngleB;
        private Vector2 _size;
        private Vector3 _stretch;
        private float _strechByMotion;
        private Vector2 _shrinkOverTime;
        private int _orientation;
        private float  _velocityFriction;
        private Vector3 _gravity;
        private Vector4 _turbulence;
        private Vector2 _textureCells;
        private Color4 _colorStart;
        private Vector4 _colorVariation;
        private Color4 _colorEnd;
        private Vector2 _fadeWithAge;
        private Vector2 _focus;
        private Vector2 _fadeTooClose;
        private int _randomSeed;
        private int _parameterMode;
        private float _length;
        private Vector3 _upVector;
        private float _turbulenceFadeIn;
        private Vector3 _emitDirection;
        private float _turbulenceSplit;

        private Vector3 _emitCenter;
        
        private Vector3 _previousEmitCenter;

        bool UpdateParametersFromInputs(OperatorPartContext context, List<OperatorPart> inputs)
        {
            //>>> _params
            var Code = inputs[(int)InputId.Code].Eval(context).Text;
            var Geometry = inputs[(int)InputId.Geometry];
            var MaxCount = inputs[(int)InputId.MaxCount].Eval(context).Value;
            var EmitCount = inputs[(int)InputId.EmitCount].Eval(context).Value;
            var LifeTime = inputs[(int)InputId.LifeTime].Eval(context).Value;
            var ResetTrigger = inputs[(int)InputId.ResetTrigger].Eval(context).Value;
            var EmitFrom = (int) inputs[(int)InputId.EmitFrom].Eval(context).Value;
            var EmitterSizeWidth = inputs[(int)InputId.EmitterSizeWidth].Eval(context).Value;
            var EmitterSizeHeight = inputs[(int)InputId.EmitterSizeHeight].Eval(context).Value;
            var EmitterSizeDepth = inputs[(int)InputId.EmitterSizeDepth].Eval(context).Value;
            var EmitterSize = new Vector3(EmitterSizeWidth, EmitterSizeHeight, EmitterSizeDepth);
            var LimitPositions = inputs[(int)InputId.LimitPositions].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 EmitVelocity = inputs[(int)InputId.EmitVelocity].Eval(context).Value;
            var EmitAngleAValue = inputs[(int)InputId.EmitAngleAValue].Eval(context).Value;
            var EmitAngleAScatter = inputs[(int)InputId.EmitAngleAScatter].Eval(context).Value;
            var EmitAngleA = new Vector2(EmitAngleAValue, EmitAngleAScatter);
            var EmitAngleBValue = inputs[(int)InputId.EmitAngleBValue].Eval(context).Value;
            var EmitAngleBScatter = inputs[(int)InputId.EmitAngleBScatter].Eval(context).Value;
            var EmitAngleB = new Vector2(EmitAngleBValue, EmitAngleBScatter);
            var SizeValue = inputs[(int)InputId.SizeValue].Eval(context).Value;
            var SizeRandom = inputs[(int)InputId.SizeRandom].Eval(context).Value;
            var Size = new Vector2(SizeValue, SizeRandom);
            var StretchX = inputs[(int)InputId.StretchX].Eval(context).Value;
            var StretchY = inputs[(int)InputId.StretchY].Eval(context).Value;
            var StretchZ = inputs[(int)InputId.StretchZ].Eval(context).Value;
            var Stretch = new Vector3(StretchX, StretchY, StretchZ);
            var StrechByMotion = inputs[(int)InputId.StrechByMotion].Eval(context).Value;
            var ShrinkOverTimeAtBeginning = inputs[(int)InputId.ShrinkOverTimeAtBeginning].Eval(context).Value;
            var ShrinkOverTimeAtEnd = inputs[(int)InputId.ShrinkOverTimeAtEnd].Eval(context).Value;
            var ShrinkOverTime = new Vector2(ShrinkOverTimeAtBeginning, ShrinkOverTimeAtEnd);
            var Orientation = (int) inputs[(int)InputId.Orientation].Eval(context).Value;
            var VelocityFriction = inputs[(int)InputId.VelocityFriction].Eval(context).Value;
            var GravityX = inputs[(int)InputId.GravityX].Eval(context).Value;
            var GravityY = inputs[(int)InputId.GravityY].Eval(context).Value;
            var GravityZ = inputs[(int)InputId.GravityZ].Eval(context).Value;
            var Gravity = new Vector3(GravityX, GravityY, GravityZ);
            var TurbulenceAmount = inputs[(int)InputId.TurbulenceAmount].Eval(context).Value;
            var TurbulenceTime = inputs[(int)InputId.TurbulenceTime].Eval(context).Value;
            var TurbulenceVariationScale = inputs[(int)InputId.TurbulenceVariationScale].Eval(context).Value;
            var TurbulenceVariation = inputs[(int)InputId.TurbulenceVariation].Eval(context).Value;
            var Turbulence = new Vector4(TurbulenceAmount, TurbulenceTime, TurbulenceVariationScale, TurbulenceVariation);
            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 ColorStartR = inputs[(int)InputId.ColorStartR].Eval(context).Value;
            var ColorStartG = inputs[(int)InputId.ColorStartG].Eval(context).Value;
            var ColorStartB = inputs[(int)InputId.ColorStartB].Eval(context).Value;
            var ColorStartA = inputs[(int)InputId.ColorStartA].Eval(context).Value;
            var ColorStart = new Color4(ColorStartR, ColorStartG, ColorStartB, ColorStartA);
            var ColorVariationHue = inputs[(int)InputId.ColorVariationHue].Eval(context).Value;
            var ColorVariationSatuation = inputs[(int)InputId.ColorVariationSatuation].Eval(context).Value;
            var ColorVariationBrightness = inputs[(int)InputId.ColorVariationBrightness].Eval(context).Value;
            var ColorVariationAlpha = inputs[(int)InputId.ColorVariationAlpha].Eval(context).Value;
            var ColorVariation = new Vector4(ColorVariationHue, ColorVariationSatuation, ColorVariationBrightness, ColorVariationAlpha);
            var ColorEndR = inputs[(int)InputId.ColorEndR].Eval(context).Value;
            var ColorEndG = inputs[(int)InputId.ColorEndG].Eval(context).Value;
            var ColorEndB = inputs[(int)InputId.ColorEndB].Eval(context).Value;
            var ColorEndA = inputs[(int)InputId.ColorEndA].Eval(context).Value;
            var ColorEnd = new Color4(ColorEndR, ColorEndG, ColorEndB, ColorEndA);
            var FadeWithAgeAtBeginning = inputs[(int)InputId.FadeWithAgeAtBeginning].Eval(context).Value;
            var FadeWithAgeAtEnd = inputs[(int)InputId.FadeWithAgeAtEnd].Eval(context).Value;
            var FadeWithAge = new Vector2(FadeWithAgeAtBeginning, FadeWithAgeAtEnd);
            var FocusCenter = inputs[(int)InputId.FocusCenter].Eval(context).Value;
            var FocusRange = inputs[(int)InputId.FocusRange].Eval(context).Value;
            var Focus = new Vector2(FocusCenter, FocusRange);
            var FadeTooCloseDistance = inputs[(int)InputId.FadeTooCloseDistance].Eval(context).Value;
            var FadeTooCloseSmooth = inputs[(int)InputId.FadeTooCloseSmooth].Eval(context).Value;
            var FadeTooClose = new Vector2(FadeTooCloseDistance, FadeTooCloseSmooth);
            var RandomSeed = inputs[(int)InputId.RandomSeed].Eval(context).Value;
            var EmitCenterX = inputs[(int)InputId.EmitCenterX].Eval(context).Value;
            var EmitCenterY = inputs[(int)InputId.EmitCenterY].Eval(context).Value;
            var EmitCenterZ = inputs[(int)InputId.EmitCenterZ].Eval(context).Value;
            var EmitCenter = new Vector3(EmitCenterX, EmitCenterY, EmitCenterZ);
            var NoiseTexture = inputs[(int)InputId.NoiseTexture].Eval(context).Image; // Needs to be checked for null!
            var ParametersAffect = (int) inputs[(int)InputId.ParametersAffect].Eval(context).Value;
            var Length = inputs[(int)InputId.Length].Eval(context).Value;
            var UpVectorX = inputs[(int)InputId.UpVectorX].Eval(context).Value;
            var UpVectorY = inputs[(int)InputId.UpVectorY].Eval(context).Value;
            var UpVectorZ = inputs[(int)InputId.UpVectorZ].Eval(context).Value;
            var UpVector = new Vector3(UpVectorX, UpVectorY, UpVectorZ);
            var TurbulenceFadeIn = inputs[(int)InputId.TurbulenceFadeIn].Eval(context).Value;
            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 TurbulenceSplit = inputs[(int)InputId.TurbulenceSplit].Eval(context).Value;
            //<<< _params

            _geometry = Geometry;

            var parameterChanged = false;

            if (_maxCount != (int)MaxCount)
            {
                _maxCount = (int)MaxCount;
                parameterChanged = true;
            }

            if (_resetTrigger != ResetTrigger > 0.5f)
            {
                _resetTrigger = ResetTrigger > 0.5f;
                parameterChanged = true;
            }


            if (_lifeTime != LifeTime)
            {
                _lifeTime = LifeTime;
                parameterChanged = true;
            }

            if (_emitterShape != (EmitterShapeType) EmitFrom)
            {
                _emitterShape = (EmitterShapeType) EmitFrom;
                parameterChanged = true;
            }

            if (_emitCount != EmitCount)
            {
                _emitCount = EmitCount;
                parameterChanged = true;
            }
            if (_emitterSize != EmitterSize)
            {
                _emitterSize = EmitterSize;
                parameterChanged = true;
            }
            if (_limitPositions != LimitPositions)
            {
                _limitPositions = LimitPositions;
                parameterChanged = true;
            }
            if (_scatter != Scatter)
            {
                _scatter = Scatter;
                parameterChanged = true;
            }
            if (_emitVelocity != EmitVelocity)
            {
                _emitVelocity = EmitVelocity;
                parameterChanged = true;
            }
            if (_emitAngleA != EmitAngleA)
            {
                _emitAngleA = EmitAngleA;
                parameterChanged = true;
            }

            if (_emitAngleB != EmitAngleB)
            {
                _emitAngleB = EmitAngleB;
                parameterChanged = true;
            }

            if (_randomSeed != (int)RandomSeed)
            {
                _randomSeed = (int)RandomSeed;
                parameterChanged = true;
            }
            
            if (_emitCenter != EmitCenter)
            {
                _emitCenter = EmitCenter;
                parameterChanged = true;
            }
            
            if (_emitDirection != EmitDirection)
            {
                _emitDirection = EmitDirection;
                parameterChanged = true;
            }
            
            _size = Size;
            _stretch = Stretch;
            _strechByMotion = StrechByMotion;
            _shrinkOverTime = ShrinkOverTime;
            _orientation = Orientation;
            _velocityFriction = VelocityFriction;
            _gravity = Gravity;
            _turbulence = Turbulence;
            _textureCells = TextureCells;
            _colorStart = ColorStart;
            _colorVariation = ColorVariation;
            _colorEnd = ColorEnd;
            _fadeWithAge = FadeWithAge;
            _focus = Focus;
            _fadeTooClose = FadeTooClose;
            _turbulenceFadeIn = TurbulenceFadeIn;
            _upVector = UpVector;
            _length = Length;
            _previousEmitCenter = _emitCenter;
            _turbulenceSplit = TurbulenceSplit;

            
            if(_parameterMode != (int)ParametersAffect) {
                _parameterMode = (int)ParametersAffect;
                parameterChanged= true;
            }
            
            return parameterChanged;
        }
        #endregion

        Vector3 GetScatterVector(Random rand)
        {
            return new Vector3((float)rand.NextDouble() - 0.5f,
                                (float)rand.NextDouble() - 0.5f,
                                (float)rand.NextDouble() - 0.5f);
        }
        
        private void ResetParticles () {
            for(var i=0; i< _particles.Count(); ++i  ) {
                _particles[i]= new Particle();
            }
        }

        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            CompileCodePartsOnFirstEvaluation();            
            
            
            var parameterChanged = UpdateParametersFromInputs(context, inputs);
            if(!parameterChanged) {
            //Logger.Info("keep");
            }
            
            if( _resetTrigger || context.Time < _lastEvalTime)
                ResetParticles();

            if (_geometry.Connections.Count == 0)
                return context;

            var numInstances = _maxCount;

            var instancedMesh = GetMeshFromSceneInput(_geometry);
            if (instancedMesh == null)
                return context;

            var rand = new Random(_randomSeed);

            var prevTransform = context.ObjectTWorld;


            // Initialize particle list
            if (numInstances != _particles.Count())
            {
                _particles = new Particle[_maxCount];
                _emitIndex = 0;
                
            }
            if(context.Time != _lastEvalTime) {
                _lastEvalTime = context.Time;
                
                //if( _parameterMode == 0 &&  (parameterChanged || _lastEmitTime > context.Time)) {
                //    _lastEmitTime = context.Time - _maxCount / _emitCount;            
                //}
    
                // Emit new Particles
                //var emitPeriod = 1.0 / _emitCount;
                //var lastParticleEmitTime = _lastEmitTime - (_lastEmitTime % emitPeriod);
                //var emitTime = lastParticleEmitTime + emitPeriod;
                //emitTime = context.Time;
                
                
                
                //var emitCenterDelta = ( _emitCenter - _previousEmitCenter);
                //if( emitTime < context.Time) {
                //}
                
                //var numberOfParticlesToEmit =  (int)_emitCount;
                //var numberOfPartriclesEmitted = 0;
                //if(numberOfParticlesToEmit <1) numberOfParticlesToEmit = 1;
    
                //while (emitTime < context.Time)
                //{
                for(var emitIndex = 0; emitIndex < _emitCount ; ++emitIndex) {
                    //Logger.Info(this, "Emit index {0}", _emitIndex);
                    _lastEmitTime = context.Time;
                    float emitBatchProgress =  (float)emitIndex / (float)_emitCount ;
                    //numberOfPartriclesEmitted++;
                    
                    Vector3 particleEmitCenter = _previousEmitCenter;
                    Vector3 pos = particleEmitCenter; 
                    
                    Vector3 emitDirection = Vector3.UnitY;
                    Vector3 axisA = Vector3.UnitX;
                    Vector3 axisB = Vector3.UnitZ;
    
                    // Initialize Random
                    int overallIndex = (int)(context.Time * 100  * _emitCount);
                    rand = new Random((int)((overallIndex *1.245) % 721.2));
                    rand = new Random((int)(rand.NextDouble() * 1000)+ overallIndex);
                    
                    // Fetch some values to avoid patterns
                    for(var randIndex=0;randIndex < overallIndex % 3;++randIndex) {
                        rand.NextDouble();
                    }
                    
    
                    int indexForLimitedPositions = (int)(overallIndex % _limitPositions);
    
                    switch (_emitterShape)
                    {    
                        // Plane
                        case EmitterShapeType.Plane:
                            axisA = Vector3.UnitX;
                            axisB = Vector3.UnitY;
                            if (_limitPositions < 0.5f)
                            {
                                pos = new Vector3((float)(rand.NextDouble() - 0.5),
                                                  (float)(rand.NextDouble() - 0.5),
                                                  (float)(rand.NextDouble() - 0.5)) * _emitterSize + particleEmitCenter;
                            }
                            else
                            {
                                float ratio = _emitterSize.X / _emitterSize.Z;
                                float columns = (int)(_limitPositions * ratio) + 1;
                                float row = indexForLimitedPositions % columns;
                                float col = indexForLimitedPositions - columns * row;
                                pos = new Vector3(_emitterSize.X / columns * col, 0, _emitterSize.Z / columns * row);
                            }
                            
                            pos+= _scatter * GetScatterVector(new Random( indexForLimitedPositions ) );
                            break;
                        
                        // Sphere
                        case EmitterShapeType.Sphere:
                        {
                            if (_limitPositions < 0.5f)
                            {
                                pos = new Vector3((float)(rand.NextDouble() - 0.5),
                                                  (float)(rand.NextDouble() - 0.5),
                                                  (float)(rand.NextDouble() - 0.5)) * _emitterSize;
                                                  
                                var angX = (float)(rand.NextDouble() * Math.PI*2);    
                                var angY = (float)(rand.NextDouble() * Math.PI*2);
                                var rot = Matrix.RotationAxis( Vector3.UnitX, angX)
                                    * Matrix.RotationAxis( Vector3.UnitY, angY);
                                    
                                pos= Vector3.Transform(Vector3.UnitZ , rot).ToVector3() * _emitterSize;
                                axisA = Vector3.Transform(Vector3.UnitX, rot).ToVector3();
                                axisB = Vector3.Transform(Vector3.UnitY, rot).ToVector3();
                               
                                emitDirection = pos;
                                emitDirection.Normalize();
                                pos += particleEmitCenter;
                                
                                
                            }
                            else
                            {
                                var inc = Math.PI*(3 - Math.Sqrt(5));
                                var off = 2.0f/(_limitPositions + 1.0f);
                                var y = indexForLimitedPositions*off - 1.0f + (off/2.0f);
                                var r = Math.Sqrt(1 - y*y);
                                var Radius = 1;
                                var phi = indexForLimitedPositions*inc;
                                pos += new Vector3((float) (Math.Cos(phi)*r*Radius),
                                                (float) (y*Radius),
                                                (float) (Math.Sin(phi)*r*Radius)) * _emitterSize;
                                                                            
                                emitDirection = pos;
                                emitDirection.Normalize();
    
                            }
                            break;
                            
                            
                        }
                    }
    
                    var rotA = Matrix.RotationAxis(axisA, _emitAngleA.X * TO_RAD + ((float)rand.NextDouble() - 0.5f) * _emitAngleA.Y * TO_RAD);
                    var rotB = Matrix.RotationAxis(axisB, _emitAngleB.X * TO_RAD + ((float)rand.NextDouble() - 0.5f) * _emitAngleB.Y * TO_RAD);
                    var randomRotation = rotA * rotB;

                    
                    //emitDirection = Vector3.Transform(emitDirection, randomRotation).ToVector3();
                    emitDirection = Vector3.Normalize(_emitDirection);
                    emitDirection += _upVector;
                    
                    _particles[_maxCount- ( _emitIndex % _maxCount)-1] = new Particle()
                    {
                        EmitTime = (float)context.Time,
                        EmitPosition = pos,
                        EmitVelocity = emitDirection * _emitVelocity,
                        EmitSize = 1
                    };;
                    _emitIndex++;
                    
                    //emitTime += emitPeriod;    
                }
            }

            // Set Parameters for shader
            SetScalar("Time", context.Time);
            SetVector("Gravity", _gravity);
            SetVector2("Size", _size);
            SetVector2("ShrinkOverTime", _shrinkOverTime);
            float lifeTime = _lifeTime<=0 ? _maxCount / _emitCount  : _lifeTime;            
            SetScalar("LifeTime", lifeTime);
            SetVector("Stretch", _stretch);
            SetVector4("Turbulence", _turbulence);
            SetScalar("VelocityFriction", _velocityFriction);
            SetScalar("Orientation", _orientation);
            SetColor("ColorStart", _colorStart);
            SetColor("ColorEnd", _colorEnd);
            SetVector4("ColorVariation", _colorVariation);
            SetVector("UpVector", _upVector);
            SetVector2("FadeWithAge", _fadeWithAge);
            SetVector2("FadeTooClose", _fadeTooClose);            
            SetScalar("Length", _length);
            SetScalar("TurbulenceFadeIn", _turbulenceFadeIn);
            SetVector("EmitDirection", _emitDirection);
            SetScalar("RandomSeed", _randomSeed);
            SetScalar("TurbulenceSplit", _turbulenceSplit);
            
            var noiseTexture = inputs[(int)InputId.NoiseTexture].Eval(context).Image; // Needs to be checked for null!
            if(noiseTexture != null) {
                var noiseTextureView = new ShaderResourceView(context.D3DDevice, noiseTexture);
                _effect.GetVariableByName("NoiseTexture").AsShaderResource().SetResource(noiseTextureView);
            }
            

            // Instance data buffer
            var streamSize = numInstances * Particle.BLOCK_SIZE;
            if (_instanceDataBuffer == null || _instanceDataBuffer.Description.SizeInBytes != streamSize)
            {
                Utilities.DisposeObj(ref _instanceDataBuffer);
                _instanceDataBuffer = new Buffer(context.D3DDevice, streamSize, ResourceUsage.Dynamic, BindFlags.VertexBuffer,
                                                 CpuAccessFlags.Write, ResourceOptionFlags.None, Particle.BLOCK_SIZE);
            }

            // Render instances
            DataStream instanceDataStream;
            context.D3DDevice.ImmediateContext.MapSubresource(_instanceDataBuffer, MapMode.WriteDiscard, MapFlags.None, out instanceDataStream);

            using (instanceDataStream)
            {
                instanceDataStream.Position = 0;
                for (var i = 0; i < numInstances;  i++)
                //for (var i = numInstances -1; i > 0; i--)
                {
                    var p = _particles[i];
                    p.WriteToDataStream(instanceDataStream);
                }
            }
            context.D3DDevice.ImmediateContext.UnmapSubresource(_instanceDataBuffer, 0);

            var prevEffect = context.Effect;
            var prevRenderer = context.Renderer;
            context.Effect = _effect;
            context.Renderer = _renderer;

            try
            {
                _renderer.SetupEffect(context);

                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;

                var technique = _effect.GetTechniqueByIndex(0);
                var pass = technique.GetPassByIndex(0);

                context.D3DDevice.ImmediateContext.Rasterizer.SetViewport(context.Viewport);
                context.D3DDevice.ImmediateContext.InputAssembler.InputLayout = new InputLayout(context.D3DDevice, pass.Description.Signature, _instanceDataInputElements);
                context.D3DDevice.ImmediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
                context.D3DDevice.ImmediateContext.InputAssembler.SetVertexBuffers(0, new[]
                {
                    instancedMesh.Vertices, 
                    _instanceDataBuffer
                },
                new[]
                {
                    VERTEX_ATTRIBUTE_SIZE, 
                    Particle.BLOCK_SIZE
                },
                new[] { 0, 0 });

                pass.Apply(context.D3DDevice.ImmediateContext);
                context.D3DDevice.ImmediateContext.DrawInstanced(instancedMesh.NumTriangles * 3, numInstances, 0, 0);

            }
            catch (Exception exception)
            {
                Logger.Error(this, "Replicate2 - An error occured during evaluation: {0}", exception.Message);
            }

            context.Effect = prevEffect;
            context.Renderer = prevRenderer;

            return context;
        }



        private Mesh GetMeshFromSceneInput(OperatorPart sceneInput)
        {
            _meshCollector.Clear();
            sceneInput.TraverseWithFunction(_meshCollector, null);
            var meshSupplier = _meshCollector.CollectedOpPartFunctions.FirstOrDefault();
            if (meshSupplier == null)
            {
                Logger.Error(this, "Found no mesh supplier, have you forgotten to add an input?");
                return null;
            }
            var meshes = new List<Mesh>();
            meshSupplier.AddMeshesTo(meshes);
            if (meshes.Count != 1)
            {
                Logger.Error(this, "Found more or less than 1 mesh");
                return null;
            }
            var instancedMesh = meshes[0];

            return instancedMesh;
        }

        #region Noise functions

        public float Noise(int x, int seed)
        {
            int n = x + seed * 137;
            n = (n << 13) ^ n;
            return (float)(1.0 - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);
        }

        public float Lerp(float a, float b, float t)
        {
            return a + t * (b - a);
        }

        public float Fade(float t)
        {
            return t * t * t * (t * (t * 6 - 15) + 10);
        }

        public float Interpolate(float a, float b, float t)
        {
            float ft = t * 3.1415927f;
            float f = (float)(1.0 - Math.Cos(ft)) * 0.5f;
            return a * (1.0f - f) + b * f;
        }

        private float getNoise(float value)
        {
            const int SEED = 0;
            float noiseSum = 0.0f;
            value *= _frequency;
            value += _noiseTime + 10000;


            noiseSum = Lerp(Noise((int)value, SEED),
                            Noise((int)value + 1, SEED),
                            Fade(value - (float)Math.Floor(value)));
            return noiseSum;
        }

        #endregion

        private const float _frequency = 1;
        private float _noiseTime;

        private OperatorPart.CollectOpPartFunctionsOfType<Framefield.Core.OperatorPartTraits.IMeshSupplier> _meshCollector = new OperatorPart.CollectOpPartFunctionsOfType<Framefield.Core.OperatorPartTraits.IMeshSupplier>();
        private Renderer _renderer;
        private Buffer _instanceDataBuffer;

        private double _lastEmitTime;
        private double _lastEvalTime;
        private bool _firstEval = true;
        private int _emitIndex = 0;
        private Particle[] _particles = new Particle[1];


        internal struct Particle
        {
            public float EmitTime;
            public float EmitSize;
            public Vector3 EmitPosition;
            public Vector3 EmitVelocity;

            public void WriteToDataStream(DataStream instanceDataStream)
            {
                instanceDataStream.Write(EmitTime);
                instanceDataStream.Write(EmitSize);
                instanceDataStream.Write(EmitPosition);
                instanceDataStream.Write(EmitVelocity);
            }

            public static int BLOCK_SIZE { get { return (1 + 1 + 3 + 3) * 4; } }
        }

        const int VERTEX_ATTRIBUTE_SIZE = 76;
        const float TO_RAD = (float)(Math.PI / 180.0f);

        private const int VERTEX_BUFFER_SLOT = 0;
        private const int INSTANCE_BUFFER_SLOT = 1;

        private readonly InputElement[] _instanceDataInputElements
            = new[]
            {
                // Slot 1 - Vertex buffer
                new InputElement("POSITION", 0, SharpDX.DXGI.Format.R32G32B32A32_Float,  0, VERTEX_BUFFER_SLOT),
                new InputElement("NORMAL",   0, SharpDX.DXGI.Format.R32G32B32_Float,    16, VERTEX_BUFFER_SLOT),
                new InputElement("COLOR",    0, SharpDX.DXGI.Format.R32G32B32A32_Float, 28, VERTEX_BUFFER_SLOT),
                new InputElement("TEXCOORD", 0, SharpDX.DXGI.Format.R32G32_Float,       44, VERTEX_BUFFER_SLOT),
                new InputElement("TANGENT",  0, SharpDX.DXGI.Format.R32G32B32_Float,    52, VERTEX_BUFFER_SLOT),
                new InputElement("BINORMAL", 0, SharpDX.DXGI.Format.R32G32B32_Float,    64, VERTEX_BUFFER_SLOT),

                // Slot 2 - Instance buffer
                new InputElement("EMIT_TIME",       0, SharpDX.DXGI.Format.R32_Float,       0*4   ,INSTANCE_BUFFER_SLOT, InputClassification.PerInstanceData, 1),
                new InputElement("EMIT_SIZE",       0, SharpDX.DXGI.Format.R32_Float,       1*4   ,INSTANCE_BUFFER_SLOT, InputClassification.PerInstanceData, 1),
                new InputElement("EMIT_POSITION",   0, SharpDX.DXGI.Format.R32G32B32_Float,  2*4  ,INSTANCE_BUFFER_SLOT, InputClassification.PerInstanceData, 1),
                new InputElement("EMIT_VELOCITY",   0, SharpDX.DXGI.Format.R32G32B32_Float,  5*4  ,INSTANCE_BUFFER_SLOT, InputClassification.PerInstanceData, 1),
            };
    }
}

