using System;
using System.Windows.Forms;
using SlimDX;
using SlimDX.D3DCompiler;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using Device = SlimDX.Direct3D11.Device;
using Resource = SlimDX.Direct3D11.Resource;

namespace Sample
{
    public partial class Engine : IDisposable
    {
        readonly Device device11;
        readonly DeviceContext immediateContext;
        readonly SwapChain swapChain;
        readonly Texture2D backBuffer;
        readonly RenderTargetView renderView;
        readonly Effect effect;
        readonly EffectPass pass;

        public Engine(Form parentForm)
        {
            // Création de notre device (on accepte les cartes dx11 uniquement)
            FeatureLevel[] levels = {
                                        FeatureLevel.Level_11_0
                                    };

            // Définition de notre swap chain
            SwapChainDescription desc = new SwapChainDescription();
            desc.BufferCount = 1;
            desc.Usage = Usage.BackBuffer | Usage.RenderTargetOutput;
            desc.ModeDescription = new ModeDescription(0, 0, new Rational(0, 0), Format.R8G8B8A8_UNorm);
            desc.SampleDescription = new SampleDescription(1, 0);
            desc.OutputHandle = parentForm.Handle;
            desc.IsWindowed = true;
            desc.SwapEffect = SwapEffect.Discard;

            Device.CreateWithSwapChain(null, DriverType.Reference, DeviceCreationFlags.Debug, levels, desc, out device11, out swapChain);

            // Récupération du contexte immédiat
            immediateContext = device11.ImmediateContext;

            // Récupération du buffer
            backBuffer = Resource.FromSwapChain<Texture2D>(swapChain, 0);

            // Definition de la cible de rendu
            renderView = new RenderTargetView(device11, backBuffer);
            immediateContext.OutputMerger.SetTargets(renderView);
            immediateContext.Rasterizer.SetViewports(new Viewport(0, 0, parentForm.ClientSize.Width, parentForm.ClientSize.Height, 0.0f, 1.0f));

            // Compilation du shader   
            using (ShaderBytecode byteCode = ShaderBytecode.CompileFromFile("Simple.hlsl", "bidon", "fx_5_0",
                                                                     ShaderFlags.OptimizationLevel3, EffectFlags.None))
            {
                effect = new Effect(device11, byteCode);
            }
            const int vertexSize = 20;
            var technique = effect.GetTechniqueByIndex(0);
            pass = technique.GetPassByIndex(0);
            var layout = new InputLayout(device11, pass.Description.Signature, new[] {
                             new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0),
                             new InputElement("TEXCOORD", 0, Format.R32G32_Float, 12, 0)
                });

            // Generation du plan
            var stream = new DataStream(4 * vertexSize, true, true);
            stream.WriteRange(new[] 
            {
                  -1.0f, -1.0f, 0f, 0f, 1.0f,
                  1.0f, -1.0f, 0f, 1.0f, 1.0f,
                  1.0f, 1.0f, 0f, 1.0f, 0.0f,
                  -1.0f, 1.0f, 0f, 0.0f, 0.0f,
            });
            stream.Position = 0;

            var vertices = new SlimDX.Direct3D11.Buffer(device11, stream, new BufferDescription
            {
                BindFlags = BindFlags.VertexBuffer,
                CpuAccessFlags = CpuAccessFlags.None,
                OptionFlags = ResourceOptionFlags.None,
                SizeInBytes = (int)stream.Length,
                Usage = ResourceUsage.Default
            });
            stream.Dispose();

            immediateContext.InputAssembler.InputLayout = layout;
            immediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            immediateContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertices, vertexSize, 0));

            // Liste des 2 faces
            stream = new DataStream(6 * 2, true, true);
            stream.WriteRange(new[] 
            {
                  (short)0, (short)1, (short)2,
                  (short)0, (short)2, (short)3
            });
            stream.Position = 0;

            var indices = new SlimDX.Direct3D11.Buffer(device11, stream, new BufferDescription
            {
                BindFlags = BindFlags.IndexBuffer,
                CpuAccessFlags = CpuAccessFlags.None,
                OptionFlags = ResourceOptionFlags.None,
                SizeInBytes = (int)stream.Length,
                Usage = ResourceUsage.Default
            });
            stream.Dispose();

            immediateContext.InputAssembler.SetIndexBuffer(indices, Format.R16_UInt, 0);
            // Texture
            Texture2D texture2D = Texture2D.FromFile(device11, "yoda.jpg");
            ShaderResourceView view = new ShaderResourceView(device11, texture2D);

            effect.GetVariableByName("diffuseTexture").AsResource().SetResource(view);

            RasterizerStateDescription rasterizerStateDescription = new RasterizerStateDescription();

            rasterizerStateDescription.CullMode = CullMode.None;
            rasterizerStateDescription.FillMode = FillMode.Solid;

            immediateContext.Rasterizer.State = RasterizerState.FromDescription(device11, rasterizerStateDescription);
        }

        public void Render()
        {
            // Linkage
            CheckLinkage();

            // Matrices
            Matrix worldMatrix = Matrix.Translation(0, 0, 5.0f);
            Matrix projectionMatrix = Matrix.PerspectiveFovLH(0.8f, 4.0f / 3.0f, 0.1f, 1000.0f);

            effect.GetVariableByName("worldProjectionMatrix").AsMatrix().SetMatrix(worldMatrix * projectionMatrix);

            // Rendu
            pass.Apply(device11.ImmediateContext);
            device11.ImmediateContext.DrawIndexed(6, 0, 0);
            swapChain.Present(0, PresentFlags.None);
        }

        #region IDisposable Members

        public void Dispose()
        {
            effect.Dispose();
            renderView.Dispose();
            backBuffer.Dispose();
            immediateContext.Dispose();
            device11.Dispose();
        }

        #endregion

        internal void ClearRender()
        {
            immediateContext.ClearRenderTargetView(renderView, new Color4(1.0f, 0, 0, 1.0f));
        }
    }
}
