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.ID28d3de44_e8ac_453c_a9e0_07d69ed05c02
{
    public class Class_ParticleReplicator1 : 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_ParticleReplicator1()
        {
            _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,
            EmitRate = 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
        }
        //<<< _inputids

        enum EmitterShapeType
        {
            Plane = 0,
            Sphere,
            Ring
        }

        private OperatorPart _geometry;
        private EmitterShapeType _emitterShape;
        private float _emitRate;
        private int _maxCount;
        private float _lifeTime;
        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 Vector3 _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 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 EmitRate = inputs[(int)InputId.EmitRate].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 Turbulence = new Vector3(TurbulenceAmount, TurbulenceTime, TurbulenceVariationScale);
            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;
            //<<< _params

            _geometry = Geometry;

            var parameterChanged = false;

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

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

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

            if (_emitRate != EmitRate)
            {
                _emitRate = EmitRate;
                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 (_size != Size)
            {
                _size = Size;
                //parameterChanged = true;
            }

            if (_stretch != Stretch)
            {
                _stretch = Stretch;
                //parameterChanged = true;
            }
            if (_strechByMotion != StrechByMotion)
            {
                _strechByMotion = StrechByMotion;
                //parameterChanged = true;
            }
            if (_shrinkOverTime != ShrinkOverTime)
            {
                _shrinkOverTime = ShrinkOverTime;
                //parameterChanged = true;
            }
            if (_orientation != Orientation)
            {
                _orientation = Orientation;
                //parameterChanged = true;
            }
            if (_velocityFriction != VelocityFriction)
            {
                _velocityFriction = VelocityFriction;
                //parameterChanged = true;
            }
            if (_gravity != Gravity)
            {
                _gravity = Gravity;
                //parameterChanged = true;
            }
            if (_turbulence != Turbulence)
            {
                _turbulence = Turbulence;
                //parameterChanged = true;
            }

            if (_textureCells != TextureCells)
            {
                _textureCells = TextureCells;
                //parameterChanged = true;
            }
            if (_colorStart != ColorStart)
            {
                _colorStart = ColorStart;
                //parameterChanged = true;
            }
            if (_colorVariation != ColorVariation)
            {
                _colorVariation = ColorVariation;
                //parameterChanged = true;
            }
            if (_colorEnd != ColorEnd)
            {
                _colorEnd = ColorEnd;
                //parameterChanged = true;
            }
            if (_fadeWithAge != FadeWithAge)
            {
                _fadeWithAge = FadeWithAge;
                //parameterChanged = true;
            }
            if (_focus != Focus)
            {
                _focus = Focus;
                //parameterChanged = true;
            }
            if (_fadeTooClose != FadeTooClose)
            {
                _fadeTooClose = FadeTooClose;
                //parameterChanged = true;
            }
            if (_randomSeed != (int)RandomSeed)
            {
                _randomSeed = (int)RandomSeed;
                parameterChanged = true;
            }
            
            _previousEmitCenter = _emitCenter;
            if (_emitCenter != EmitCenter)
            {
                _emitCenter = EmitCenter;
                parameterChanged = true;
            }
            
            
            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);
        }

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

            var parameterChanged = UpdateParametersFromInputs(context, inputs);
            if(!parameterChanged) {
            //Logger.Info("keep");
            }

            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( _parameterMode == 0 &&  (parameterChanged || _lastTime > context.Time)) {
                _lastTime = context.Time - _maxCount / _emitRate;            
            }

            // Emit new Particles
            var emitPeriod = 1.0 / _emitRate;
            var lastParticleEmitTime = _lastTime - (_lastTime % emitPeriod);
            var emitTime = lastParticleEmitTime + emitPeriod;
            //emitTime = context.Time;
            _lastTime = context.Time;
            
            
            var emitCenterDelta = ( _emitCenter - _previousEmitCenter);
            if( emitTime < context.Time) {
                //Logger.Info("n:{0}" , emitCenterDelta);
            }
            
            var numberOfParticlesToEmit =  (int)((context.Time-emitTime)*_emitRate ) ;
            var numberOfPartriclesEmitted = 0;
            if(numberOfParticlesToEmit <1) numberOfParticlesToEmit = 1;
            
            

            while (emitTime < context.Time)
            {
                float emitBatchProgress =  (float)numberOfPartriclesEmitted / (float)numberOfParticlesToEmit ;
                numberOfPartriclesEmitted++;
                
                Vector3 particleEmitCenter = _previousEmitCenter + emitCenterDelta * emitBatchProgress;
                Vector3 pos = particleEmitCenter; 
                
                Vector3 emitDirection = Vector3.UnitY;
                Vector3 axisA = Vector3.UnitX;
                Vector3 axisB = Vector3.UnitZ;

                // Initialize Random
                int overallIndex = (int)(emitTime * _emitRate);
                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 ) );
                        //emitDirection = po
                        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*EmitterSize.X + EmitterSize.Y - 1 + (off/2);
                            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;
                                            
                            //axisA = new Vector3((float) (Math.Sin(phi)*r*Radius),
                            //                (float) (y*Radius),
                            //                (float) (Math.Sin(phi)*r*Radius));
                            //axisA.Normalize();

                            //axisB = new Vector3((float) Math.Sin(y*Math.PI),
                            //                1,
                            //                0);
                            
                            emitDirection = pos;
                            emitDirection.Normalize();

                        }
                        break;
                        
                        
                    }
                    /*
                    // Ring
                    case 2:
                    {
                        float l1 = i%(int) (Layer.X*Layer.Y);
                        float l2 = i%(int) Layer.Y;
                        float a = (i - l1)/MaxCount;
                        t = new Vector3((float) (Math.Sin(Math.PI*2*a)*(Radius + l1*Stretch.X)),
                                        (l2/Layer.Y - 0.5f)*Stretch.Y,
                                        (float) (Math.Cos(Math.PI*2*a)*(Radius + l1*Stretch.Z)));
                        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.Transform(emitDirection, rotB).ToVector3();
                
                _particles[_emitIndex % _maxCount] = new Particle()
                {
                    EmitTime = (float)emitTime,
                    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 / _emitRate  : _lifeTime;            
            SetScalar("LifeTime", lifeTime);
            SetVector("Stretch", _stretch);
            SetVector("Turbulence", _turbulence);
            SetScalar("VelocityFriction", _velocityFriction);
            SetScalar("Orientation", _orientation);
            SetColor("ColorStart", _colorStart);
            SetColor("ColorEnd", _colorEnd);
            SetVector4("ColorVariation", _colorVariation);
            SetVector2("FadeWithAge", _fadeWithAge);
            SetVector2("FadeTooClose", _fadeTooClose);
            
            
            var noiseTexture = inputs[(int)InputId.NoiseTexture].Eval(context).Image; // Needs to be checked for null!
            //if(noiseTexture != null)
            //    SetResource("NoiseTexture", noiseTexture);
            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 = numInstances -1; i > 0; i--)
                {



                    /*
                                        if (NoiseAmount != 0)
                                        {
                                            _noiseTime = NoiseTime;
                                            _frequency = 1/NoiseScale;
                                            var noiseOffset = new Vector3(getNoise(t.X / Radius - 6.3f), getNoise(t.Y / Radius + 9.3f), getNoise((t.Z - 0.3f)));
                                            t += noiseOffset * NoiseAmount;
                                        }

                                        float s = Size + (float)rand.NextDouble() * SizeRandom;
                                        var scale = new Vector3(s, s, s);
                    
                                        //Matrix rotation;
                                        Matrix transform;
                                        Matrix.RotationYawPitchRoll(Rotate.Y * toRad + (float)(rand.NextDouble()) * RotateRandom.Y * toRad,
                                                                    Rotate.X * toRad + (float)(rand.NextDouble()) * RotateRandom.X * toRad,
                                                                    Rotate.Z * toRad + (float)(rand.NextDouble()) * RotateRandom.Z * toRad,
                                                                    out transform);
                    
                                                                    
                                        var tScale = Matrix.Scaling( scale.X, scale.Y, scale.Z);
                                        transform= tScale * transform;
                                        var t2= Matrix.Translation(new Vector3(OffsetX + (float)rand.NextDouble() * (OffsetRandomX + Scatter),
                                                                                    OffsetY + (float)rand.NextDouble() * (OffsetRandomY + Scatter),
                                                                                    OffsetZ + (float)rand.NextDouble() * (OffsetRandomZ + Scatter)));
                                        transform = t2*transform;

                                        if (Orientation == 0)
                                        {
                                            if (t.Length() > 0)
                                            {
                                                var dir = -t;
                                                dir.Normalize();
                                                var helperDir = new Vector3(1, 0, 0);
                                                if (Math.Abs(dir.Y) < 0.1 && Math.Abs(dir.Z) < 0.1)
                                                    helperDir = new Vector3(0, 1, 0);
                                                var yAxis = Vector3.Cross(dir, helperDir);
                                                yAxis.Normalize();
                                                var xAxis = Vector3.Cross(yAxis, dir);
                                                xAxis.Normalize();
                                                var m = Matrix.Identity;
                                                m.Row1 = new Vector4(xAxis, 0);
                                                m.Row2 = new Vector4(yAxis, 0);
                                                m.Row3 = new Vector4(dir, 0);
                                                transform *= m;
                                            }
                                        }
                                        else if (Orientation == 2)
                                        {
                                            //if (Shape == 2)
                                            //{
                                            //    var rotationAroundCenter = Matrix.Identity;
                                            //    Matrix.RotationY((float)(2 * Math.PI * i / Count), out rotationAroundCenter);
                                            //    transform *= rotationAroundCenter;
                                            //}
                                            var cameraToWorld = context.WorldToCamera;
                                            cameraToWorld.Invert();
                                            var newObjectToWorld = context.ObjectTWorld * cameraToWorld;
                                            newObjectToWorld.Row4 = context.ObjectTWorld.Row4;
                                            transform *=    newObjectToWorld;                     
                        
                                        }*/

                    //transform *= Matrix.Transformation(new Vector3(), new Quaternion(), new Vector3(1,1,1), new Vector3(), new Quaternion(), t);
                    //transform *= prevTransform; 


                    var p = _particles[i];
                    //var age = context.Time - p.EmitTime;
                    //var position = p.EmitPosition + p.EmitVelocity * age;
                    //var transform = Matrix.Translation(position);
                    //transform *= prevTransform;

                    p.WriteToDataStream(instanceDataStream);

                    //instanceDataStream.Write(transform);
                }
            }
            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 _lastTime;
        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),
            };
    }
}

