using MoonWorks;
using MoonWorks.Audio;
using MoonWorks.Graphics;
using MoonWorks.Input;
using MoonWorks.Window;
using System.IO;
using System.Threading;

namespace MoonWorksTest
{
    public class TestGame : Game
    {
        ShaderModule passthroughVertexShaderModule;
        ShaderModule raymarchFragmentShaderModule;
        RaymarchUniforms raymarchUniforms;

        Texture woodTexture;
        Texture noiseTexture;
        Sampler sampler;

        Buffer vertexBuffer;

        Rect renderArea;
        Rect flip;
        Color clearColor;
        DepthStencilValue depthStencilClear;

        Texture mainColorTargetTexture;
        TextureSlice mainColorTargetTextureSlice;
        RenderTarget mainColorTarget;

        RenderPass mainRenderPass;
        Framebuffer mainFramebuffer;
        GraphicsPipeline mainGraphicsPipeline;

        byte[] screenshotPixels;
        Buffer screenshotBuffer;
        uint screenShotBufferSize;
        Thread screenshotThread;

        StaticSound music;
        StaticSoundInstance musicInstance;
        StreamingSoundOgg musicStream;

        public TestGame(WindowCreateInfo windowCreateInfo, PresentMode presentMode, int targetTimestep = 60, bool debugMode = false) : base(windowCreateInfo, presentMode, targetTimestep, debugMode)
        {
            var windowWidth = windowCreateInfo.WindowWidth;
            var windowHeight = windowCreateInfo.WindowHeight;

            passthroughVertexShaderModule = new ShaderModule(GraphicsDevice, Path.Combine("Content", "passthrough_vert.spv"));
            raymarchFragmentShaderModule = new ShaderModule(GraphicsDevice, Path.Combine("Content", "hexagon_grid.spv"));

            raymarchUniforms.time = 0;
            raymarchUniforms.padding = 0;
            raymarchUniforms.resolutionX = windowWidth;
            raymarchUniforms.resolutionY = windowHeight;

            var uploadCommandBuffer = GraphicsDevice.AcquireCommandBuffer();

            woodTexture = Texture.LoadPNG(GraphicsDevice, uploadCommandBuffer, "Content/woodgrain.png");
            noiseTexture = Texture.LoadPNG(GraphicsDevice, uploadCommandBuffer, "Content/noise.png");

            sampler = new Sampler(GraphicsDevice, SamplerCreateInfo.LinearWrap);

            /* Load Vertex Data */

            var vertices = new Vertex[3];
            vertices[0].x = -1;
            vertices[0].y = -1;
            vertices[0].z = 0;
            vertices[0].u = 0;
            vertices[0].v = 1;

            vertices[1].x = 3;
            vertices[1].y = -1;
            vertices[1].z = 0;
            vertices[1].u = 1;
            vertices[1].v = 1;

            vertices[2].x = -1;
            vertices[2].y = 3;
            vertices[2].z = 0;
            vertices[2].u = 0;
            vertices[2].v = 0;

            vertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, 4 * 5 * 3);
            uploadCommandBuffer.SetBufferData(vertexBuffer, vertices);

            GraphicsDevice.Submit(uploadCommandBuffer);

            /* Render Pass */

            renderArea.X = 0;
            renderArea.Y = 0;
            renderArea.W = (int) windowWidth;
            renderArea.H = (int) windowHeight;

            flip.X = 0;
            flip.Y = (int) windowHeight;
            flip.W = (int) windowWidth;
            flip.H = -(int) windowHeight;

            clearColor.R = 237;
            clearColor.G = 41;
            clearColor.B = 57;
            clearColor.A = byte.MaxValue;

            ColorTargetDescription colorTargetDescription = new ColorTargetDescription
            {
                Format = TextureFormat.R8G8B8A8,
                MultisampleCount = SampleCount.One,
                LoadOp = LoadOp.Clear,
                StoreOp = StoreOp.Store
            };

            mainRenderPass = new RenderPass(GraphicsDevice, colorTargetDescription);

            mainColorTargetTexture = Texture.CreateTexture2D(
                GraphicsDevice,
                windowWidth,
                windowHeight,
                TextureFormat.R8G8B8A8,
                TextureUsageFlags.ColorTarget
            );

            mainColorTargetTextureSlice = new TextureSlice(mainColorTargetTexture);
            mainColorTarget = new RenderTarget(GraphicsDevice, mainColorTargetTextureSlice);

            mainFramebuffer = new Framebuffer(
                GraphicsDevice,
                windowWidth,
                windowHeight,
                mainRenderPass,
                null,
                mainColorTarget
            );

            /* Pipeline */

            ColorTargetBlendState[] colorTargetBlendStates = new ColorTargetBlendState[1]
            {
                ColorTargetBlendState.None
            };

            ColorBlendState colorBlendState = new ColorBlendState
            {
                LogicOpEnable = false,
                LogicOp = LogicOp.NoOp,
                BlendConstants = new BlendConstants(),
                ColorTargetBlendStates = colorTargetBlendStates
            };

            DepthStencilState depthStencilState = DepthStencilState.Disable;

            ShaderStageState vertexShaderState = new ShaderStageState
            {
                ShaderModule = passthroughVertexShaderModule,
                EntryPointName = "main",
                UniformBufferSize = 0
            };

            ShaderStageState fragmentShaderState = new ShaderStageState
            {
                ShaderModule = raymarchFragmentShaderModule,
                EntryPointName = "main",
                UniformBufferSize = 16
            };

            MultisampleState multisampleState = MultisampleState.None;

            GraphicsPipelineLayoutInfo pipelineLayoutInfo = new GraphicsPipelineLayoutInfo
            {
                VertexSamplerBindingCount = 0,
                FragmentSamplerBindingCount = 2
            };

            RasterizerState rasterizerState = RasterizerState.CW_CullBack;

            var vertexBindings = new VertexBinding[1]
            {
                new VertexBinding
                {
                    Binding = 0,
                    InputRate = VertexInputRate.Vertex,
                    Stride = 4 * 5
                }
            };

            var vertexAttributes = new VertexAttribute[2]
            {
                new VertexAttribute
                {
                    Binding = 0,
                    Location = 0,
                    Format = VertexElementFormat.Vector3,
                    Offset = 0
                },
                new VertexAttribute
                {
                    Binding = 0,
                    Location = 1,
                    Format = VertexElementFormat.Vector2,
                    Offset = 4 * 3
                }
            };

            VertexInputState vertexInputState = new VertexInputState
            {
                VertexBindings = vertexBindings,
                VertexAttributes = vertexAttributes
            };

            var viewports = new Viewport[1]
            {
                new Viewport
                {
                    X = 0,
                    Y = 0,
                    W = windowWidth,
                    H = windowHeight,
                    MinDepth = 0,
                    MaxDepth = 1
                }
            };

            var scissors = new Rect[1]
            {
                new Rect
                {
                    X = 0,
                    Y = 0,
                    W = (int) windowWidth,
                    H = (int) windowHeight
                }
            };

            ViewportState viewportState = new ViewportState
            {
                Viewports = viewports,
                Scissors = scissors
            };

            var graphicsPipelineCreateInfo = new GraphicsPipelineCreateInfo
            {
                ColorBlendState = colorBlendState,
                DepthStencilState = depthStencilState,
                VertexShaderState = vertexShaderState,
                FragmentShaderState = fragmentShaderState,
                MultisampleState = multisampleState,
                PipelineLayoutInfo = pipelineLayoutInfo,
                RasterizerState = rasterizerState,
                PrimitiveType = PrimitiveType.TriangleList,
                VertexInputState = vertexInputState,
                ViewportState = viewportState,
                RenderPass = mainRenderPass
            };

            mainGraphicsPipeline = new GraphicsPipeline(
                GraphicsDevice,
                graphicsPipelineCreateInfo
            );

            screenShotBufferSize = windowWidth * windowHeight * 4;
            screenshotPixels = new byte[screenShotBufferSize];
            screenshotBuffer = new Buffer(GraphicsDevice, 0, screenShotBufferSize);
            screenshotThread = new Thread(new ThreadStart(SaveScreenshot));

            music = StaticSound.LoadOgg(AudioDevice, Path.Combine("Content", "title_screen.ogg"));
            musicInstance = music.CreateInstance();
            // musicInstance.Play();

            musicStream = StreamingSoundOgg.Load(AudioDevice, Path.Combine("Content", "title_screen.ogg"), false, true);
            musicStream.Play();
        }

        protected override void Update(System.TimeSpan dt)
        {
            raymarchUniforms.time += (float)dt.TotalSeconds;
        }

        protected override void Draw(System.TimeSpan dt, double alpha)
        {
            var screenshotPressed = Inputs.Keyboard.IsPressed(Keycode.S);

            var commandBuffer = GraphicsDevice.AcquireCommandBuffer();

            commandBuffer.BeginRenderPass(
                mainRenderPass,
                mainFramebuffer,
                renderArea,
                depthStencilClear,
                clearColor.ToVector4()
            );

            commandBuffer.BindGraphicsPipeline(mainGraphicsPipeline);

            commandBuffer.BindVertexBuffers(0, new BufferBinding(vertexBuffer, 0));
            commandBuffer.BindFragmentSamplers(
                new TextureSamplerBinding(woodTexture, sampler),
                new TextureSamplerBinding(noiseTexture, sampler)
            );

            var fragmentParamOffset = commandBuffer.PushFragmentShaderUniforms(raymarchUniforms);
            commandBuffer.DrawPrimitives(0, 1, 0, fragmentParamOffset);
            commandBuffer.EndRenderPass();

            if (screenshotPressed)
            {
                commandBuffer.CopyTextureToBuffer(mainColorTargetTextureSlice, screenshotBuffer);
            }

            commandBuffer.QueuePresent(mainColorTargetTextureSlice, flip, Filter.Nearest);
            GraphicsDevice.Submit(commandBuffer);

            if (screenshotPressed)
            {
                screenshotThread.Start();
            }
        }

        private void SaveScreenshot()
        {
            GraphicsDevice.Wait();
            screenshotBuffer.GetData(screenshotPixels, screenShotBufferSize);
            Texture.SavePNG("screenshot.png", 1280, 720, screenshotPixels);
        }
    }
}
