using System;
using System.Collections.Generic;
using SharpDX;
using SharpDX.Direct3D11;
using Buffer = SharpDX.Direct3D11.Buffer;

namespace Framefield.Core.IDe1757897_3b7c_408d_89a8_cdb892d6749b
{
    public class Class_Trail : OperatorPart.Function
    {
        //>>> _inputids
        private enum InputId
        {
            PositionX = 0,
            PositionY = 1,
            PositionZ = 2,
            ResetTrigger = 3,
            MaxSteps = 4,
            UVMode = 5,
            Width = 6,
            MinDistance = 7,
            TextureScale = 8,
            UpVectorX = 9,
            UpVectorY = 10,
            UpVectorZ = 11,
            ColorR = 12,
            ColorG = 13,
            ColorB = 14,
            ColorA = 15,
            Twist = 16
        }
        //<<< _inputids

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


        void Reset()
        {
        	_steps = 0;
        	_lastTextureU = 0;
        	RebuildVertexLists(0);
        	//_previousStepIndex= -1;
        	//paintedUpdates.setValue(0);
        }
        
        void positionChangedCB()
        {
        	//Trail* trail = (Trail*)data;
        	
        	SetupVertices();
        	//paintedUpdates.setValue( trail->paintedUpdates.getValue() + 1 );
        }
        
        Vector3 _position;        
        Vector3 _lastPosition;
        Vector3 _up;
        Vector3 _previousExtendVector;
        Vector3 _slagPosition;  // moving behind position to stabilize rotation axis

        float _width;
        int _steps = 0;
        //int _previousStepIndex = -1;
        int _maxSteps;
        //int _maxStepsCached;
        int _uvMode;
        int _paintedUpdates = 0;
        float _textureScale;
        float _lastTextureU;
        float _minDistance;
        float _paintedLength;
        float _twist;

        const int TIME=0, LENGTH=1, EXTERNAL=2;        

        void SetupVertices()
        {        	
        	if(_steps == 0) {        
        		_lastPosition = _position;
        		++_steps;
                _vertices.Add(_position);
                _vertices.Add(_position);
                _texUs.Add(0);
        		return;
        	}
        
        	// Calculate last two vertices 
        	Vector3 diff = _position - _lastPosition;
        	
        	float friction= 0.99f;
        	_slagPosition =  friction * _position + (1-friction) * _slagPosition;
        	Vector3 axis = _position - _slagPosition;
        	
        	var m = Matrix.Identity;
        	Matrix.RotationAxis(ref axis, _twist/180.0f*3.141578f, out m);
            //Matrix.RotationAxis(ref diff, 20f, out m);

            Vector3 extendVec = Vector3.Cross(diff,_up);
            var rotatedVec= Vector3.Transform( extendVec, m);
            extendVec = new Vector3( rotatedVec.X, rotatedVec.Y, rotatedVec.Z);
        	extendVec.Normalize();
        	extendVec *= _width/2;

            // Handle different time modes
        	float textureU = 0;
        	switch (_uvMode)
        	{
                case TIME:
        			textureU = _paintedUpdates / _textureScale/_steps;
        			break;
        		case LENGTH:
        			textureU = _lastTextureU + diff.Length() / _textureScale;
        			break;
        		case EXTERNAL:
        			//_textureU = _textureU;    // Set externally
                    break;
        		default:
        			textureU = _lastTextureU + diff.Length() * _textureScale;
        			break;
        	}
        	

        
        	// Change angle of previous line
            //Vector3 averageExtendVec = (_previousExtendVector + _extendVector) * 0.5f;		
        	//averageExtendVec.Normalize();
        	//averageExtendVec *= _width * 0.5f;
        	//_vertices[stepIndex- 2  ] = _lastPosition + averageExtendVec;
        	//_vertices[stepIndex- 2+1] = _lastPosition - averageExtendVec;

            int stepIndex = Math.Min( _texUs.Count, Math.Min(_steps, _maxSteps));

            // Make a Step        	
        	if(diff.Length() > _minDistance) {

                // Shift oldest vertex to make room for a new entry
                if (stepIndex == _maxSteps) {
                    _vertices.RemoveAt(0);
                    _vertices.RemoveAt(0);
                    _texUs.RemoveAt(0);
                }

        		_previousExtendVector = extendVec;
        
        		_vertices.Add(_position + extendVec);
        		_vertices.Add(_position - extendVec);        		
        		_texUs.Add(textureU);
        									 
        		++_steps;
        		_lastPosition = _position;
        		_lastTextureU = textureU;
        	}
        	else {
        		_vertices[stepIndex*2-2  ] = _position + extendVec;
        		_vertices[stepIndex*2-1] = _position - extendVec;
        			
        		_texUs[stepIndex-1] = textureU;        		
        	}
        	_paintedLength= textureU;
        }
        

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

            var PositionX = inputs[(int)InputId.PositionX].Eval(context).Value;
            var PositionY = inputs[(int)InputId.PositionY].Eval(context).Value;
            var PositionZ = inputs[(int)InputId.PositionZ].Eval(context).Value;
            _position = new Vector3(PositionX, PositionY, PositionZ);
            var ResetTrigger = inputs[(int)InputId.ResetTrigger].Eval(context).Value;
            _maxSteps = (int)inputs[(int)InputId.MaxSteps].Eval(context).Value;
            _uvMode = (int) inputs[(int)InputId.UVMode].Eval(context).Value;
            _width = inputs[(int)InputId.Width].Eval(context).Value;
            _minDistance = inputs[(int)InputId.MinDistance].Eval(context).Value;
            _textureScale = inputs[(int)InputId.TextureScale].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;
            _up = new Vector3(UpVectorX, UpVectorY, UpVectorZ);
            var ColorR = inputs[(int)InputId.ColorR].Eval(context).Value;
            var ColorG = inputs[(int)InputId.ColorG].Eval(context).Value;
            var ColorB = inputs[(int)InputId.ColorB].Eval(context).Value;
            var ColorA = inputs[(int)InputId.ColorA].Eval(context).Value;
            var Color = new Color4(ColorR, ColorG, ColorB, ColorA);
            _twist = inputs[(int)InputId.Twist].Eval(context).Value;

            //int verticeCount = (_maxSteps+1)*2;
            //if (_vertices.Count != verticeCount) {
                //RebuildVertexLists(verticeCount);
            //}

            if (_planeMesh == null || Changed) {
                // Function>>> 
                var normal = new Vector3(0.0f, 0.0f, -1.0f);
                var color = Color;
                var tangent = new Vector3(1.0f, 0.0f, 0.0f);
                var binormal = new Vector3(0.0f, -1.0f, 0.0f);
                    
                var inputElements = new InputElement[] {
                    new InputElement("POSITION", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 0, 0),
                    new InputElement("NORMAL", 0, SharpDX.DXGI.Format.R32G32B32_Float, 16, 0),
                    new InputElement("COLOR", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 28, 0),
                    new InputElement("TEXCOORD", 0, SharpDX.DXGI.Format.R32G32_Float, 44, 0),
                    new InputElement("TANGENT", 0, SharpDX.DXGI.Format.R32G32B32_Float, 52, 0),
                    new InputElement("BINORMAL", 0, SharpDX.DXGI.Format.R32G32B32_Float, 64, 0)
                };

        	   if(ResetTrigger > 0.5f) {
        	       Reset();
        	   }                    

                if (_position != _lastPosition) {
                    SetupVertices();
                }

                if (_steps < 2)
                    return context;
                    
                
                // Setup vertex-structure
                const int attributesSize = 76;
                int numVertexBufferElements = (_maxSteps + 1) * 2;
                int numTriangles = _vertices.Count < 4 ? 0 : _vertices.Count - 2;
                int streamSize = numVertexBufferElements * 3 * attributesSize;

                if (_planeMesh == null || streamSize != _planeMesh.Vertices.Description.SizeInBytes) 
                {
                    Utilities.DisposeObj(ref _planeMesh);
                    using (var stream = new DataStream(streamSize, true, true))
                    {
                        var vertices = new Buffer(context.D3DDevice, stream, new BufferDescription
                                           {
                                               BindFlags = BindFlags.VertexBuffer,
                                               CpuAccessFlags = CpuAccessFlags.Write,
                                               OptionFlags = ResourceOptionFlags.None,
                                               SizeInBytes = streamSize,
                                               Usage = ResourceUsage.Dynamic
                                           });
                        _planeMesh = new Mesh { InputElements = inputElements, Vertices = vertices, NumTriangles = numTriangles, AttributesSize = attributesSize };
                    }
                }
                if (_planeMesh.NumTriangles != numTriangles)
                {
                    _planeMesh.NumTriangles = numTriangles;
                }

                DataStream vertexStream;
                DataBox box = context.D3DDevice.ImmediateContext.MapSubresource(_planeMesh.Vertices, MapMode.WriteDiscard, MapFlags.None, out vertexStream);
                using (vertexStream)
                {
                    vertexStream.Position = 0;
    
                    float uShift = _paintedLength;
    
                    for (int i = 0; i < _vertices.Count/2 - 1; i += 1) {
                        // tri 1 vert 1
                        vertexStream.Write(new Vector4(_vertices[i*2], 1));
                        vertexStream.Write(normal);
                        vertexStream.Write(color);
                        vertexStream.Write( new Vector2(_texUs[i]-uShift,0));
                        vertexStream.Write(tangent);
                        vertexStream.Write(binormal);
    
                        // tri 1 vert 2
                        vertexStream.Write(new Vector4(_vertices[i*2 + 1], 1));
                        vertexStream.Write(normal);
                        vertexStream.Write(color);
                        vertexStream.Write(new Vector2(_texUs[i]-uShift, 1));
                        vertexStream.Write(tangent);
                        vertexStream.Write(binormal);
    
                        // tri 1 vert 3
                        vertexStream.Write(new Vector4(_vertices[i*2 + 2], 1));
                        vertexStream.Write(normal);
                        vertexStream.Write(color);
                        vertexStream.Write(new Vector2(_texUs[i+1]-uShift, 0));
                        vertexStream.Write(tangent);
                        vertexStream.Write(binormal);
    
                        // tri 2 vert 1
                        vertexStream.Write(new Vector4(_vertices[i*2 + 1], 1));
                        vertexStream.Write(normal);
                        vertexStream.Write(color);
                        vertexStream.Write(new Vector2(_texUs[i]-uShift, 1));
                        vertexStream.Write(tangent);
                        vertexStream.Write(binormal);
    
                        // tri 2 vert 2
                        vertexStream.Write(new Vector4(_vertices[i*2 + 3], 1));
                        vertexStream.Write(normal);
                        vertexStream.Write(color);
                        vertexStream.Write(new Vector2(_texUs[i+1]-uShift, 1));
                        vertexStream.Write(tangent);
                        vertexStream.Write(binormal);
    
                        // tri 2 vert 3
                        vertexStream.Write(new Vector4(_vertices[i*2 + 2], 1));
                        vertexStream.Write(normal);
                        vertexStream.Write(color);
                        vertexStream.Write(new Vector2(_texUs[i+1]-uShift, 0));
                        vertexStream.Write(tangent);
                        vertexStream.Write(binormal);
                    }
    
    
                    context.D3DDevice.ImmediateContext.UnmapSubresource(_planeMesh.Vertices, 0);
                }

                Changed = false;
            }

            context.Renderer.SetupEffect(context);
            context.Renderer.Render(_planeMesh, context);

            return context;
        }

        private void RebuildVertexLists(int verticeCount) {
            _vertices.Clear();
            _texUs.Clear();
        }

        private List<Vector3> _vertices = new List<Vector3>(100);
        private List<float> _texUs = new List<float>(100);
        

        private Mesh _planeMesh;
    }
}