/*
 * 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 simple vector-based shapes with 2D canvas to a "drawable" texture.
//  * Real-time Gaussian blur effect.
//  * Color dithering to reduce quantization errors when converting from 64-bits per pixel to 32-bit and
//    remove visible banding effects.

namespace DrawableTextures
{
  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 GaussianBlur gaussianBlur;
    private ColorDithering colorDithering;

    private Texture textureCanvasMSAA;
    private Texture textureCanvas;
    private Texture textureBlurred;
    private Texture textureBlurredCopy;
    private Texture textureBlurredDithered;

    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));
        gaussianBlur = new GaussianBlur(device, 3.0f, 64, true);
        colorDithering = new ColorDithering(device);
        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();
      colorDithering?.Dispose();
      gaussianBlur?.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));
      }
    }

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

      // Draw to a multisampled texture.
      textureCanvasMSAA.Clear(FloatColor.White);
      textureCanvasMSAA.Begin();
      try
      {
        RandomSequence prng = new(1200);

        canvas.Begin();
        try
        {
          canvas.SamplerState = default;
          canvas.ContextState = CanvasContextState.FlatScene;
          canvas.Attributes = Canvas.Attribute.Transform;

          float hexSize = 32.0f;
          float hexDelta = 1.1547f;
          float interleave = 0.0f; // 1.0f;

          for (int j = 0; j < displaySize.Y / hexSize; ++j)
            for (int i = 0; i < displaySize.X / hexSize; ++i)
            {
              float offset = (j % 2) * (hexSize + interleave * 0.5f);

              uint color = (new FloatColorRGB(
                prng.Ranged(256), prng.Ranged(256), prng.Ranged(256))).ToColorAlpha;

              canvas.Transform = Matrix3x2.CreateScale(hexSize) *
                Matrix3x2.CreateTranslation(
                  i * (interleave + hexSize * 2.0f) + offset,
                  j * (interleave + (hexSize * 2.0f) / hexDelta));

              canvas.Hexagon(
                Utility.AverageColors(color, 0x80FFFFFF),
                Utility.MultiplyColors(color, 0x80E0E0E0),
                Utility.MultiplyColors(color, 0x80D0D0D0),
                Utility.MultiplyColors(color, 0x80C0C0C0),
                Utility.MultiplyColors(color, 0x80909090),
                Utility.MultiplyColors(color, 0x80808080));
            }

          canvas.Attributes = 0;
          float kappa = (float)(ticks * 0.0274);

          canvas.Arc(new Vector2(256.0f, 128.0f), new Vector2(64.0f, 64.0f), kappa, kappa + (float)(
            1.25 * Math.PI + Math.Sin(ticks * 0.01854) * 0.5 * Math.PI), 32, new ColorRect(0xFF00FF00u,
            0xFFFF0000u, 0xFF0000FFu, 0xFFFFFFFFu), BlendingEffect.Copy);

          kappa = (float)(ticks * 0.02231);

          canvas.Arc(new Vector2(256.0f, 384.0f), new Vector2(25.0f, 20.0f), 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);

          canvas.ContextState = CanvasContextState.FlatText;
          textRenderer.Parameters = new FontParameters("Segoe UI", 16.0f, FontWeight.SemiBold);
          textRenderer.DrawAligned(new Vector2(256.0f, 510.0f), "Some text on drawable texture.",
            new ColorPair(0xFF000000u), TextAlignment.Middle, TextAlignment.End);

          textRenderer.Parameters = new FontParameters("Verdana", 24.0f, FontWeight.Bold);
          textRenderer.DrawAligned(new Vector2(256.0f, 256), "Bold text for blurring purposes.",
            new ColorPair(0xFF000000u));
        }
        finally
        {
          canvas.End();
        }
      }
      finally
      {
        textureCanvasMSAA.End();
      }
      textureCanvas.Copy(textureCanvasMSAA); // Resolve MSAA to non-MSAA texture.

      // Perform the blur.
      gaussianBlur.Sigma = float.Lerp(0.001f, 20.0f, (float)Utility.SineCycle((float)(ticks * 0.007)));
      gaussianBlur.Update(textureBlurred, textureBlurredCopy, textureCanvas);

      // Perform color dithering to eliminate color banding that results from conversion to 8-bit channels.
      colorDithering.Format = ColorDitheringFormat.RGB8;
      colorDithering.Execute(textureBlurredDithered, textureBlurred);

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

        canvas.Begin();
        try
        {
          canvas.SamplerState = CanvasSamplerState.Default;
          canvas.ContextState = CanvasContextState.FlatScene;

          // Draw resolved canvas texture.
          canvas.Quad(textureCanvas, new Quad(displaySize.X * 0.5f - 512.0f, displaySize.Y * 0.5f - 256.0f,
            512.0f, 512.0f), null, null, BlendingEffect.Copy);

          // Draw blurred canvas texture.
          canvas.Quad(textureBlurredDithered, new Quad(displaySize.X * 0.5f, displaySize.Y * 0.5f - 256.0f,
            512.0f, 512.0f), null, null, BlendingEffect.Copy);

          // 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 = 512;
      parameters.Height = 512;
      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);
      textureBlurred = new Texture(device, parameters);
      textureBlurredCopy = new Texture(device, parameters);

      parameters.Format = PixelFormat.RGBA8;

      textureBlurredDithered = new Texture(device, parameters);
    }

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

      textureBlurredCopy?.Dispose();
      textureBlurredCopy = null;

      textureBlurred?.Dispose();
      textureBlurred = null;

      textureCanvas?.Dispose();
      textureCanvas = null;

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