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.IDb2865c6d_64e3_4b32_867c_fcafaefd28e3
{
    public class Class_GridWithDisplace : 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_GridWithDisplace()
        {
            _renderer = new Renderer(){ParentFunc = this};
        }

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

        //>>> _inputids
        private enum InputId
        {
            Code = 0,
            SceneInput = 1,
            CountColumns = 2,
            CountRows = 3,
            SpacingX = 4,
            SpacingY = 5,
            InitialScaleX = 6,
            InitialScaleY = 7,
            DisplaceScaleX = 8,
            DisplaceScaleY = 9,
            InitialRotate = 10,
            DisplaceRotate = 11,
            DisplaceMap = 12,
            Pattern = 13,
            Shrinking = 14,
            SpiralOffset = 15,
            SpiralSpin = 16
        }
        //<<< _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 CountColumns = inputs[(int)InputId.CountColumns].Eval(context).Value;
            var CountRows = inputs[(int)InputId.CountRows].Eval(context).Value;
            var Count = new Vector2(CountColumns, CountRows);
            var SpacingX = inputs[(int)InputId.SpacingX].Eval(context).Value;
            var SpacingY = inputs[(int)InputId.SpacingY].Eval(context).Value;
            var Spacing = new Vector2(SpacingX, SpacingY);
            var InitialScaleX = inputs[(int)InputId.InitialScaleX].Eval(context).Value;
            var InitialScaleY = inputs[(int)InputId.InitialScaleY].Eval(context).Value;
            var InitialScale = new Vector2(InitialScaleX, InitialScaleY);
            var DisplaceScaleX = inputs[(int)InputId.DisplaceScaleX].Eval(context).Value;
            var DisplaceScaleY = inputs[(int)InputId.DisplaceScaleY].Eval(context).Value;
            var DisplaceScale = new Vector2(DisplaceScaleX, DisplaceScaleY);
            var InitialRotate = inputs[(int)InputId.InitialRotate].Eval(context).Value;
            var DisplaceRotate = inputs[(int)InputId.DisplaceRotate].Eval(context).Value;
            var DisplaceMap = inputs[(int)InputId.DisplaceMap].Eval(context).Image; // Needs to be checked for null!
            var Pattern = (int) inputs[(int)InputId.Pattern].Eval(context).Value;
            var Shrinking = (int) inputs[(int)InputId.Shrinking].Eval(context).Value;
            var SpiralOffset = inputs[(int)InputId.SpiralOffset].Eval(context).Value;
            var SpiralSpin = inputs[(int)InputId.SpiralSpin].Eval(context).Value;
            var Spiral = new Vector2(SpiralOffset, SpiralSpin);
            //<<< _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) CountColumns * (int)CountRows;
            
            if(Pattern == 1) { // Grid
                numInstances = (int) CountColumns * (int)CountRows / 2;
            }
                        
            if (numInstances <0) {
                return context;
            }            
            
            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;
                var countInstances = 0;
                for (var row = 0; row < (int)CountRows; ++row)
                {
                    for (var column = 0; column < (int)CountColumns; ++column) {
                    
                        bool isOdd = (column + row) %2 >0;
                        if( (isOdd && Pattern == 1) || countInstances >= numInstances ) 
                            continue;
                            
                        Matrix transform;                        
                        Vector3 t = new Vector3();
                        
                        // Triangular-pattern
                        if(Pattern == 2)
                        {
                            var triangleOffset= (Pattern == 2 && isOdd)
                                                ? (float)(0.331 * SpacingY) 
                                                : 0;
                            t = new Vector3((float) ((column - CountColumns/2 + 0.5) * SpacingX * (0.581)),
                                            (float) ((row - CountRows/2 + 0.5) * SpacingY + triangleOffset),
                                            (float) (0));
                            var rotateAroundZ = isOdd ? 120*3.141578 / 360 :0;
                            transform = Matrix.RotationZ((float)rotateAroundZ);

                        }
                        // Spiral
                        else if (Pattern == 3) {
                            var n = column + row * (int)CountColumns + 3;
                            
                            var angle =  (double)n * (137.5096 + (double)SpiralOffset / 100.0) + SpiralSpin * Math.PI/180; //137.508;
                            var radius = (double)Spacing.X * Math.Sqrt(n);
                            t = new Vector3((float) ( Math.Sin( angle) * radius )  ,
                                            (float) ( Math.Cos( angle) * radius),
                                            (float) (0));
                            var rotateAroundZ = -angle;
                            transform = Matrix.RotationZ((float)rotateAroundZ);
                            //transform = Matrix.Identity;
                        
                        }
                        // Grid
                        else {
                            t = new Vector3((float) ((column - CountColumns/2 + 0.5) * SpacingX),
                                            (float) ((row - CountRows/2 + 0.5)* SpacingY),
                                            (float) (0));
                            transform = Matrix.Identity;
                        }
                        
                                                
                        transform *= Matrix.Transformation(new Vector3(), new Quaternion(), new Vector3(1,1,1), new Vector3(), new Quaternion(), t);
                        transform *= prevTransform; 

                        instanceDataStream.Write(transform);
                        countInstances ++;
                    }
               }
            }
            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.SetViewports(new [] { 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);
                
                
                //_effect.GetVariableByName("DisplaceMap").AsShaderResource().SetResource(DisplaceMapView);
                    var DisplaceMapView = new ShaderResourceView(context.D3DDevice, DisplaceMap);
                    _effect.GetVariableByName("DisplaceMap").AsShaderResource().SetResource(DisplaceMapView);                    
                    _effect.GetVariableByName("InitialRotate").AsScalar().Set(InitialRotate);
                    _effect.GetVariableByName("DisplaceRotate").AsScalar().Set(DisplaceRotate);
                    _effect.GetVariableByName("InitialScale").AsVector().Set(new Vector2(InitialScaleX, InitialScaleY));
                    _effect.GetVariableByName("DisplaceScale").AsVector().Set(new Vector2(DisplaceScaleX, DisplaceScaleY));
                    _effect.GetVariableByName("Spacing").AsVector().Set(new Vector2(SpacingX, SpacingY));
                    _effect.GetVariableByName("Count").AsVector().Set(new Vector2(CountColumns, CountRows));
                    _effect.GetVariableByName("Shrinking").AsScalar().Set(Shrinking);
                    
                context.D3DDevice.ImmediateContext.DrawInstanced(instancedMesh.NumTriangles*3, numInstances, 0, 0);
            }
            catch (Exception exception)
            {
                Logger.Error(this,"GridWidthDisplace - An error occured during evaluation: {0}", exception.Message);
            }

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

            return context;
        }


        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),
                                                                };
    }
}

