//>>> _using
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.Windows;
//<<< _using
using SharpDX.DXGI;
using Framefield.Core.Rendering;
using SharpDX.Direct3D;

namespace Framefield.Core.ID844f313d_e59e_4987_9bcc_dcb3185125c1
{
    public class Class_RenderToImg_MS : FXSourceCodeFunction
    {
        //>>> _inputids
        private enum InputId
        {
            Code = 0,
            Scene = 1,
            SizeX = 2,
            SizeY = 3,
            ColorR = 4,
            ColorG = 5,
            ColorB = 6,
            ColorA = 7,
            SampleCount = 8,
            ClearBackground = 9
        }
        //<<< _inputids

        public override void Dispose()
        {
            ResourceManager.Dispose(_resolvedImageResource);
            ResourceManager.Dispose(_resolvedDepthResource);
            Utilities.DisposeObj(ref _resolvedTargetDepthView);
            ResourceManager.Dispose(_renderTargetMSResource);
            Utilities.DisposeObj(ref _renderTargetMSView);
            ResourceManager.Dispose(_renderDepthMSResource);
            Utilities.DisposeObj(ref _renderTargetDepthMSView);
            Utilities.DisposeObj(ref _renderer);
            base.Dispose();
        }

        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            //>>> _params
            var Code = inputs[(int)InputId.Code].Eval(context).Text;
            var Scene = inputs[(int)InputId.Scene];
            var SizeX = inputs[(int)InputId.SizeX].Eval(context).Value;
            var SizeY = inputs[(int)InputId.SizeY].Eval(context).Value;
            var Size = new Vector2(SizeX, SizeY);
            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);
            var SampleCount = inputs[(int)InputId.SampleCount].Eval(context).Value;
            var ClearBackground = (int) inputs[(int)InputId.ClearBackground].Eval(context).Value;
            //<<< _params

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

            if (_effect == null)
                return context;

            if (_renderer == null)
                 _renderer = new BaseRenderer();

            if (SampleCount < 0.5f)
            {
                float sc;
                if (context.Variables.TryGetValue("Samples", out sc))
                    SampleCount = sc;
            }
            SampleCount = Utilities.Clamp(SampleCount, 1, 8);

//currently we only support 4xms because the depth resolving is currently not flexible enough
SampleCount = 4;

            if (SizeX == 0 || SizeY == 0)
            {
                SizeX = context.Viewport.Width;
                SizeY = context.Viewport.Height;
            }

            if (BuildRenderTarget((int)SizeX, (int)SizeY, (int)SampleCount, context.D3DDevice))
                Changed = true;

            if (Changed)
            {
                var subContext = new OperatorPartContext(context);
                var D3DDevice = context.D3DDevice;

                float aspect = (float)SizeX / (float)SizeY;
                subContext.CameraProjection = Matrix.PerspectiveFovLH(3.1415f / 4.0f, aspect, 0.01f, 1000);
                subContext.WorldToCamera = Matrix.LookAtLH(new Vector3(0, 0, -10), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
                subContext.ObjectTWorld = Matrix.Identity;
                subContext.TextureMatrix = Matrix.Identity;
                subContext.Renderer = OperatorPartContext.DefaultRenderer;
                subContext.RenderTargetView = _renderTargetMSView;
                subContext.DepthStencilView = _renderTargetDepthMSView;

                subContext.DepthStencilState = OperatorPartContext.DefaultRenderer.DefaultDepthStencilState;
                subContext.BlendState = OperatorPartContext.DefaultRenderer.DefaultBlendState;
                subContext.Viewport = new Viewport(0, 0, SizeX, SizeY, 0.0f, 1.0f);

                subContext.InputLayout = OperatorPartContext.DefaultRenderer.SceneDefaultInputLayout;
                subContext.Effect = OperatorPartContext.DefaultRenderer.SceneDefaultEffect;

                D3DDevice.ImmediateContext.ClearDepthStencilView(_renderTargetDepthMSView, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0);
                if (ClearBackground > 0.5f)
                    D3DDevice.ImmediateContext.ClearRenderTargetView(_renderTargetMSView, new SharpDX.Color4(ColorR, ColorG, ColorB, ColorA));

                Scene.Eval(subContext);

                D3DDevice.ImmediateContext.ResolveSubresource(_renderTargetMSResource.Texture, 0, _resolvedImageResource.Texture, 0,
                                                              _resolvedImageResource.Texture.Description.Format);


                //manually resolve the multisampled depth buffer
                subContext.D3DDevice.ImmediateContext.ClearDepthStencilView(_resolvedTargetDepthView, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0xff);

                var shaderDesc = new ShaderResourceViewDescription();
                shaderDesc.Format = Format.R32_Float;
                shaderDesc.Dimension = (int)SampleCount > 1 ? ShaderResourceViewDimension.Texture2DMultisampled : ShaderResourceViewDimension.Texture2D;
                shaderDesc.Texture2D.MipLevels = 1;

                using (var msDepthView = new ShaderResourceView(D3DDevice, _renderDepthMSResource.Texture, shaderDesc))
                {
                    _effect.GetVariableByName("txMSDepth").AsShaderResource().SetResource(msDepthView);
    
                    var context2 = new OperatorPartContext(subContext)
                                         {
                                             DepthStencilView = _resolvedTargetDepthView,
                                             RenderTargetView = null,
                                             Effect = _effect,
                                             Renderer = _renderer,
                                             InputLayout = subContext.Renderer.ScreenQuadInputLayout,
                                             CameraProjection = Matrix.OrthoLH(1, 1, -100, 100),
                                             WorldToCamera = Matrix.Identity,
                                             ObjectTWorld = Matrix.Identity,
                                             TextureMatrix = Matrix.Identity
                                         };
                    context2.Renderer.SetupEffect(context2);
                    context2.Renderer.Render(context2.Renderer._screenQuadMesh, context2);
                }

                Changed = false;
            }

            context.Image = _resolvedImageResource.Texture;
            context.DepthImage = _resolvedDepthResource.Texture;

            return context;
        }

        private bool BuildRenderTarget(int width, int height, int samples, SharpDX.Direct3D11.Device device)
        {
            var textureDesc = ResourceManager.GetTextureDescription(BindFlags.ShaderResource, Format.R8G8B8A8_UNorm, width, height);
            var resolvedImageResourceChanged = ResourceManager.ValidateResource(ref _resolvedImageResource, OperatorPart, device, textureDesc);

            var depthStencilResourceChanged = ResourceManager.ValidateDepthStencilResource(ref _resolvedDepthResource, OperatorPart, device,
                                                                                           width, height);
            if (depthStencilResourceChanged)
            {
                Utilities.DisposeObj(ref _resolvedTargetDepthView);

                var depthViewDesc = new DepthStencilViewDescription
                                        {
                                            Format = Format.D32_Float, 
                                            Dimension = DepthStencilViewDimension.Texture2D
                                        };

                _resolvedTargetDepthView = new DepthStencilView(device, _resolvedDepthResource.Texture, depthViewDesc);
            }


            var textureMSDesc = ResourceManager.GetTextureDescription(BindFlags.RenderTarget | BindFlags.ShaderResource, Format.R8G8B8A8_UNorm, width, height);
            textureMSDesc.SampleDescription = new SampleDescription(samples, 0);
            var renderTargetMSResourceChanged = ResourceManager.ValidateResource(ref _renderTargetMSResource, OperatorPart, device, textureMSDesc);

            if (renderTargetMSResourceChanged)
            {
                Utilities.DisposeObj(ref _renderTargetMSView);
                var viewDesc = new RenderTargetViewDescription();
                viewDesc.Format = textureMSDesc.Format;
                viewDesc.Dimension = samples > 1 ? RenderTargetViewDimension.Texture2DMultisampled : RenderTargetViewDimension.Texture2D;

                _renderTargetMSView = new RenderTargetView(device, _renderTargetMSResource.Texture, viewDesc);
            }

            var depthMSDesc = ResourceManager.GetTextureDescription(BindFlags.DepthStencil | BindFlags.ShaderResource, Format.R32_Typeless, width, height);
            depthMSDesc.SampleDescription = new SampleDescription(samples, 0);

            var depthStencilMSResourceChanged = ResourceManager.ValidateResource(ref _renderDepthMSResource, OperatorPart, device, depthMSDesc);
            if (depthStencilMSResourceChanged)
            {
                Utilities.DisposeObj(ref _renderTargetDepthMSView);

                var depthViewDesc = new DepthStencilViewDescription();
                depthViewDesc.Format = Format.D32_Float;
                depthViewDesc.Dimension = samples > 1 ? DepthStencilViewDimension.Texture2DMultisampled : DepthStencilViewDimension.Texture2D;

                _renderTargetDepthMSView = new DepthStencilView(device, _renderDepthMSResource.Texture, depthViewDesc);
            }

            return resolvedImageResourceChanged || depthStencilResourceChanged || renderTargetMSResourceChanged || depthStencilMSResourceChanged;
        }

        Resource _renderTargetMSResource;
        RenderTargetView _renderTargetMSView;
        Resource _renderDepthMSResource;
        DepthStencilView _renderTargetDepthMSView;

        Resource _resolvedImageResource;
        Resource _resolvedDepthResource;
        DepthStencilView _resolvedTargetDepthView;
        protected bool _firstEval = true;
        protected BaseRenderer _renderer;
    }
}

