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.ID94a7e91f_48c8_41c1_bb95_e177c409f7ab
{
    public class Class_PerformanceCurve : FXSourceCodeFunction
    {
        #region 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;}            
        }
        #endregion

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

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

        //>>> _inputids
        private enum InputId
        {
            Code = 0,
            SceneInput = 1,
            Count = 2,
            Value = 3
        }
        //<<< _inputids


        private bool _firstEval = true;
        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            //>>> _params
            var Code = inputs[(int)InputId.Code].Eval(context).Text;
            var SceneInput = inputs[(int)InputId.SceneInput];
            var Count = inputs[(int)InputId.Count].Eval(context).Value;
            var Value = inputs[(int)InputId.Value].Eval(context).Value;
            //<<< _params        
            
            // Update Value List
            var count = (int)Count;
            
            if( count != _lastCount) {
                _lastCount = count;
                _values = new float[count];
                _index= 0;
            }
            
            _values[_index] = Value;
            _index = (_index + 1) % count;
            
            if (SceneInput.Connections.Count == 0)
            {
                return context;
            }

            if (_firstEval)
            {
                for (int i = 0; i < NumCodes(); ++i)
                    Compile(i);
                _firstEval = false;
                Changed = true;
            }

            var prevTransform = context.ObjectTWorld;

            _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 context;
            }
            var meshes = new List<Mesh>();
            meshSupplier.AddMeshesTo(meshes);
            if (meshes.Count != 1)
            {
                Logger.Error(this,"Found more or less than 1 mesh");
                return context;
            }
            var instancedMesh = meshes[0];

            // instance data buffer
            const int instanceDataSize = 4*16; // float4x4
            var numInstances = (int) Count;
            var streamSize = numInstances * instanceDataSize;
            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, instanceDataSize);
            }

            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)
                {
                    var v = _values[(_index + i) % count];
                    
                    Vector3 t = new Vector3(i, 0.5f, 0);

                    var scale = new Vector3(1, v, 1);
                    
                    //Matrix rotation;
                    Matrix transform = Matrix.Identity;
                    
                                                                    
                    var tScale = Matrix.Scaling( scale.X, scale.Y, scale.Z);



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

                    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 [] {76, instanceDataSize}, 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 float[] _values;
        private int _lastCount = -1;
        private int _index = 0;

        private OperatorPart.CollectOpPartFunctionsOfType<Framefield.Core.OperatorPartTraits.IMeshSupplier> _meshCollector = new OperatorPart.CollectOpPartFunctionsOfType<Framefield.Core.OperatorPartTraits.IMeshSupplier>();
        private Renderer _renderer;
        private Buffer _instanceDataBuffer;
        private InputElement[] _instanceDataInputElements = new []
                                                                {
                                                                    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),
                                                                    new InputElement("INSTANCE_OBJ_TO_WORLD_ROW", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 0,
                                                                                     1, InputClassification.PerInstanceData, 1),
                                                                    new InputElement("INSTANCE_OBJ_TO_WORLD_ROW", 1, SharpDX.DXGI.Format.R32G32B32A32_Float, 16,
                                                                                     1, InputClassification.PerInstanceData, 1),
                                                                    new InputElement("INSTANCE_OBJ_TO_WORLD_ROW", 2, SharpDX.DXGI.Format.R32G32B32A32_Float, 32,
                                                                                     1, InputClassification.PerInstanceData, 1),
                                                                    new InputElement("INSTANCE_OBJ_TO_WORLD_ROW", 3, SharpDX.DXGI.Format.R32G32B32A32_Float, 48,
                                                                                     1, InputClassification.PerInstanceData, 1),
                                                                };
    }
}

