/*
 * This file is part of Afterwarp Framework (http://afterwarp.io).
 * Copyright (c) 2015 - 2025 Dr. Yuriy Kotsarenko. All rights reserved.
 *
 * This example source code is licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */

// This example illustrates the following features:
//  * Drawing with 2D canvas to a "drawable" texture. By drawing to another texture, which retains its
//    contents between the frames, it is possible to produce a "motion blur" looking effect.

namespace ColorTunnel
{
  using System.Numerics;
  using System.Windows.Forms;
  using Afterwarp;
  using TextRenderer = Afterwarp.TextRenderer;
  using Timer = Afterwarp.Timer;
  using Application = System.Windows.Forms.Application;

  public partial class MainForm : Form
  {
    private Device device;
    private SwapChain swapChain;
    private Canvas canvas;
    private TextRenderer textRenderer;
    private Timer timer;

    private Texture textureCanvasMSAA;
    private Texture textureCanvas;
    private Texture textureMotion;
    private Texture textureMotionCopy;

    private Vector2 displaySize;

    public MainForm()
    {
      InitializeComponent();
      Application.Idle += Application_Idle;
    }

    private void MainForm_Load(object sender, EventArgs e)
    {
      displaySize = new Vector2(ClientSize.Width, ClientSize.Height);
      try
      {
        device = new Device(DeviceTechnology.Direct3D);
        swapChain = new SwapChain(device, Handle);
        canvas = new Canvas(device);
        textRenderer = new TextRenderer(canvas, new Point(512, 512));
        timer = new Timer();

        CreateTextures();
      }
      catch (Exception ex)
      {
        MessageBox.Show(ex.Message, "Initialization failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
        Application.Exit();
      }
      SetStyle(ControlStyles.Opaque, true);
      SetStyle(ControlStyles.UserPaint, true);
      SetStyle(ControlStyles.AllPaintingInWmPaint, true);
      SetStyle(ControlStyles.DoubleBuffer, false);
    }

    private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
    {
      DestroyTextures();

      timer?.Dispose();
      textRenderer?.Dispose();
      canvas?.Dispose();
      swapChain?.Dispose();
      device?.Dispose();
    }

    private void MainForm_Resize(object sender, EventArgs e)
    {
      if (WindowState != FormWindowState.Minimized && device is not null)
      {
        displaySize = new Vector2(ClientSize.Width, ClientSize.Height);
        swapChain.Resize(new Point(displaySize));

        DestroyTextures();
        try
        {
          CreateTextures();
        }
        catch (Exception ex)
        {
          MessageBox.Show(ex.Message, "Re-creating textures failed.", MessageBoxButtons.OK,
            MessageBoxIcon.Error);
          Application.Exit();
        }
      }
    }

    private void MainForm_Paint(object sender, PaintEventArgs e)
    {
      float scale = DeviceDpi / 96.0f;
      double ticks = Utility.SystemTicks * 60.0 / 1000000.0;

      Quad fullQuad = new(0.0f, 0.0f, displaySize.X, displaySize.Y);
      Vector2 halfTargetSize = displaySize * 0.5f;

      textureCanvasMSAA.Clear();
      textureCanvasMSAA.Begin();
      try
      {
        canvas.Begin();
        try
        {
          canvas.ContextState = CanvasContextState.FlatScene;

          float kappa = (float)(ticks * 0.0274);
          float size = MathF.Min(displaySize.X, displaySize.Y);

          canvas.Tape(halfTargetSize, new Vector2(size * 0.25f), new Vector2(size * 0.3f, size * 0.4f),
            kappa, kappa + (float)(1.25 * Math.PI + Math.Sin(ticks * 0.024751) * 0.5 * Math.PI), 128,
            new ColorRect(0xFFFF0000u, 0xFF00FF00u, 0xFF0000FFu, 0xFFFFFFFFu), BlendingEffect.Copy);

          kappa = -(float)(ticks * 0.0132);

          canvas.Tape(halfTargetSize, new Vector2(size * 0.02f, size * 0.01f),
            new Vector2(size * 0.12f, size * 0.1f), kappa, kappa +
            (float)(1.5 * Math.PI + Math.Cos(ticks * 0.024751) * 0.5 * Math.PI), 64,
            new ColorRect(0xFFFF00FF, 0xFF00FFFF, 0xFFFFFF00, 0xFFFFFFFF), BlendingEffect.Copy);
        }
        finally
        {
          canvas.End();
        }
      }
      finally
      {
        textureCanvasMSAA.End();
      }
      textureCanvas.Copy(textureCanvasMSAA); // Resolve MSAA to non-MSAA texture.

      // Draw a kind of motion-blur effect.
      textureMotion.Begin();
      try
      {
        canvas.Begin();
        try
        {
          canvas.SamplerState = new CanvasSamplerState(TextureFilter.Linear, TextureFilter.Linear);
          canvas.ContextState = CanvasContextState.FlatScene;

          // Copy previous scene, englarged and slightly rotated.
          canvas.Attributes = Canvas.Attribute.ColorAdjust | Canvas.Attribute.Cubic;
          canvas.Quad(textureMotionCopy, Quad.Rotated(halfTargetSize, displaySize, -0.01f, 1.02f),
            new ColorRect(0xFE80807Eu), null, BlendingEffect.Copy);

          // Add the "motion scene" on our working surface.
          canvas.Attributes = 0;
          canvas.Quad(textureCanvas, fullQuad, new ColorRect(
            new FloatColor(1.0f, 1.0f, 1.0f, 0.875f).ToColor));
        }
        finally
        {
          canvas.End();
        }
      }
      finally
      {
        textureMotion.End();
      }
      textureMotionCopy.Copy(textureMotion);

      // Render on the form.
      swapChain.Begin();
      try
      {
        device.Clear(DeviceClear.Color, new FloatColor(0xFFE1E2E6u));

        canvas.Begin();
        try
        {
          canvas.Attributes = 0;
          canvas.SamplerState = CanvasSamplerState.Default;
          canvas.ContextState = CanvasContextState.FlatScene;
          canvas.Quad(textureMotion, fullQuad);

          // Render information text.
          canvas.ContextState = CanvasContextState.FlatText;
          {
            FontParameters parameters = new("Segoe UI", 16.0f * scale, FontWeight.SemiBold);
            parameters.Effect.BorderType = FontBorder.SemiHeavy;
            parameters.Effect.BorderBrightness = 1.0f;
            parameters.Effect.FillBrightness = 0.0f;
            parameters.Effect.ShadowBrightness = 0.75f;
            parameters.Effect.ShadowOpacity = 1.0f;
            textRenderer.Parameters = parameters;

            textRenderer.Draw(new Vector2(8.0f * scale, 4.0f * scale),
              $"Frame Rate: {(int)MathF.Round(timer.FrameRate)}", new ColorPair(0xA0FFFFFFu));

            textRenderer.DrawAligned(new Vector2(displaySize.X - 8.0f * scale, displaySize.Y -
              4.0f * scale), string.Format("Direct3D ({0}.{1}), features: {2}.{3}",
              device.TechVersion >> 8, (device.TechVersion >> 4) & 0xF,
              device.TechFeatureVersion >> 8, (device.TechFeatureVersion >> 4) & 0xF),
              new ColorPair(0xA0FFFFFFu), TextAlignment.End, TextAlignment.End);
          }
        }
        finally
        {
          canvas.End();
        }
      }
      finally
      {
        swapChain.End();
      }
      timer.Update();
    }

    private void Application_Idle(object sender, EventArgs e)
    {
      Invalidate(); // Continuously repaint the form.
    }

    private void CreateTextures()
    {
      TextureParameters parameters = default;
      parameters.Width = (int)displaySize.X;
      parameters.Height = (int)displaySize.Y;
      parameters.Attributes = Texture.Attribute.Drawable | Texture.Attribute.PremultipliedAlpha;
      parameters.Format = PixelFormat.RGBA16;
      parameters.Multisamples = 8;

      textureCanvasMSAA = new Texture(device, parameters);

      parameters.Multisamples = 0;

      textureCanvas = new Texture(device, parameters);
      textureMotion = new Texture(device, parameters);
      textureMotionCopy = new Texture(device, parameters);
    }

    private void DestroyTextures()
    {
      textureMotionCopy?.Dispose();
      textureMotionCopy = null;

      textureMotion?.Dispose();
      textureMotion = null;

      textureCanvas?.Dispose();
      textureCanvas = null;

      textureCanvasMSAA?.Dispose();
      textureCanvasMSAA = null;
    }
  }
}