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.ID49539223_d3de_413f_8adf_0730efde5c34
{
    public class Class_ReplicateOnSpline : 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_ReplicateOnSpline()
        {
            _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,
            PositionAX = 3,
            PositionAY = 4,
            PositionAZ = 5,
            TangentAX = 6,
            TangentAY = 7,
            TangentAZ = 8,
            PositionBX = 9,
            PositionBY = 10,
            PositionBZ = 11,
            TangentBX = 12,
            TangentBY = 13,
            TangentBZ = 14
        }
        //<<< _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 PositionAX = inputs[(int)InputId.PositionAX].Eval(context).Value;
            var PositionAY = inputs[(int)InputId.PositionAY].Eval(context).Value;
            var PositionAZ = inputs[(int)InputId.PositionAZ].Eval(context).Value;
            var PositionA = new Vector3(PositionAX, PositionAY, PositionAZ);
            var TangentAX = inputs[(int)InputId.TangentAX].Eval(context).Value;
            var TangentAY = inputs[(int)InputId.TangentAY].Eval(context).Value;
            var TangentAZ = inputs[(int)InputId.TangentAZ].Eval(context).Value;
            var TangentA = new Vector3(TangentAX, TangentAY, TangentAZ);
            var PositionBX = inputs[(int)InputId.PositionBX].Eval(context).Value;
            var PositionBY = inputs[(int)InputId.PositionBY].Eval(context).Value;
            var PositionBZ = inputs[(int)InputId.PositionBZ].Eval(context).Value;
            var PositionB = new Vector3(PositionBX, PositionBY, PositionBZ);
            var TangentBX = inputs[(int)InputId.TangentBX].Eval(context).Value;
            var TangentBY = inputs[(int)InputId.TangentBY].Eval(context).Value;
            var TangentBZ = inputs[(int)InputId.TangentBZ].Eval(context).Value;
            var TangentB = new Vector3(TangentBX, TangentBY, TangentBZ);
            //<<< _params        

            if (SceneInput.Connections.Count == 0)
            {
                return context;
            }

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

            const float toRad = (float) Math.PI/180f;

            var prevTransform = context.ObjectTWorld;

            //var meshCollector = new OperatorPart.CollectOpPartFunctionsOfType<Framefield.Core.OperatorPartTraits.IMeshSupplier>();
            _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);
                using (var stream = new DataStream(streamSize, true, true))
                {
                    _instanceDataBuffer = new Buffer(context.D3DDevice, stream, new BufferDescription
                                                                                    {
                                                                                        BindFlags = BindFlags.VertexBuffer,
                                                                                        CpuAccessFlags = CpuAccessFlags.Write,
                                                                                        OptionFlags = ResourceOptionFlags.None,
                                                                                        SizeInBytes = streamSize,
                                                                                        Usage = ResourceUsage.Dynamic
                                                                                    });
                }
            }

            DataStream instanceDataStream;
            context.D3DDevice.ImmediateContext.MapSubresource(_instanceDataBuffer, MapMode.WriteDiscard, MapFlags.None, out instanceDataStream);
            using (instanceDataStream)
            {
                instanceDataStream.Position = 0;

                for (var i = 0; i < Count-1; ++i)
                {
                    
                    float f1= i/Count;
                    float s1 =  Fade(f1);
                    Vector3 t = (1-f1) * (PositionA + s1 * TangentA) 
                    + f1 * (PositionB+ (1-s1) * TangentB);

                    float f2= (i+1)/Count;
                    float s2 =  Fade(f2);
                    Vector3 t2 = (1-f2) * (PositionA + s2 * TangentA) 
                    + f2 * (PositionB+ (1-s2) * TangentB);


                    var transform = Matrix.Identity;

                    // Rotation                    
                    var dir = t-t2;
                    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;

                    transform *= Matrix.Transformation(new Vector3(), new Quaternion(), new Vector3(1,1,1), new Vector3(), new Quaternion(), t);
                    transform *= context.ObjectTWorld;
 
                    instanceDataStream.Write(transform.Row1);
                    instanceDataStream.Write(transform.Row2);
                    instanceDataStream.Write(transform.Row3);
                    instanceDataStream.Write(transform.Row4);
                }
            }
            context.D3DDevice.ImmediateContext.UnmapSubresource(_instanceDataBuffer, 0);

            using (new PropertyStasher<OperatorPartContext>(context, "Effect", "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);
                }
            }

            return context;
        }

        #region helpers

        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)
        {
            float noiseSum = 0.0f;
            value *= _frequency;
            value += _noiseTime;


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

        #endregion

        private const int SEED = 0;
        private 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 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),
                                                                };
    }
}

