/*
 * 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 different shapes, including vector-based paths with fill and stroke using 2D canvas.

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

  public partial class MainForm : Form
  {
    private Device device;
    private SwapChain swapChain;
    private Canvas canvas;
    private TextRenderer textRenderer;
    private PathBroker pathBroker;
    private CanvasBuffer canvasBuffer;
    private RectF canvasBufferExtents;

    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, null, PixelFormat.Unknown, PixelFormat.Unknown, 8);
        canvas = new Canvas(device);
        textRenderer = new TextRenderer(canvas, new Point(512, 512), PixelFormat.LA8);

        pathBroker = new PathBroker();
        canvasBuffer = new CanvasBuffer();

        CreatePathFillAndStroke();
      }
      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)
    {
      canvasBuffer?.Dispose();
      pathBroker?.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));
        Invalidate();
      }
    }

    private void MainForm_Paint(object sender, PaintEventArgs e)
    {
      // One tick is incremented 60 times per second.
      double ticks = Utility.SystemTicks / (1000.0 * (1000.0 / 60.0));
      float scale = DeviceDpi / 96.0f;

      swapChain.Begin();
      try
      {
        device.Clear(DeviceClear.Color, new FloatColor(0xFF33444Eu));

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

          // Draw Hexagon.
          canvas.Attributes = Canvas.Attribute.Transform;
          canvas.Transform = Matrix3x2.CreateScale(50.0f * scale) *
            Matrix3x2.CreateRotation((float)(ticks * 0.00371)) *
            Matrix3x2.CreateTranslation(displaySize.X * 0.2f, displaySize.Y * 0.25f);

          canvas.Hexagon(0x000000FFu, 0xFF28D7FFu, 0x000000FFu, 0xFF28D7FFu, 0x000000FFu, 0xFF28D7FFu);

          // Draw Arc.
          float omega = (float)(ticks * 0.01892);
          float kappa = (float)(1.25 * Math.PI + Math.Sin(ticks * 0.01241) * 0.5 * Math.PI);

          canvas.Attributes = 0;
          canvas.Arc(new Vector2(displaySize.X * 0.5f, displaySize.Y * 0.25f),
            (new Vector2(70.0f, 50.0f)) * scale, omega, omega + kappa, 64,
            new ColorRect(0xFF81E5A4u, 0xFF009CFFu, 0xFFFF2877u, 0xFFFFFFFFu));

          // Draw small Tape.
          omega = (float)(ticks * 0.01134);
          kappa = (float)(1.25 * Math.PI + Math.Sin(ticks * 0.014751) * 0.5 * Math.PI);

          canvas.Tape(new Vector2(displaySize.X * 0.8f, displaySize.Y * 0.25f),
            (new Vector2(25.0f, 20.0f)) * scale, (new Vector2(45.0f, 40.0f)) * scale, omega, omega + kappa,
            64, new ColorRect(0xFF4F24FFu, 0xFF0DFFACu, 0xFFFF982Bu, 0xFFFF427Bu));

          // Draw large Tape.
          omega = (float)(ticks * 0.01721);
          kappa = (float)(1.25 * Math.PI + Math.Sin(ticks * 0.01042) * 0.5 * Math.PI);

          canvas.Tape(new Vector2(displaySize.X * 0.8f, displaySize.Y * 0.25f),
            (new Vector2(50.0f, 45.0f)) * scale, (new Vector2(70.0f, 65.0f)) * scale, omega, omega + kappa,
            64, new ColorPair(0xFF4F24FFu, 0xFF0DFFACu), new ColorPair(0xFFFF982Bu, 0xFF81E5A4u),
            new ColorPair(0xFF009CFFu, 0xFFFF2877u));

          // Draw Hole with smooth internal border (using Ribbon).
          Vector2 holeAt = new(
            displaySize.X * 0.75f + MathF.Cos((float)(ticks * 0.00718)) * displaySize.X * 0.15f,
            displaySize.Y * 0.75f + MathF.Sin((float)(ticks * 0.00912)) * displaySize.Y * 0.15f);

          Vector2 holeSize = (new Vector2(40.0f, 40.0f)) * scale;

          canvas.RectWithHole(new RectF(displaySize.X * 0.5f, displaySize.Y * 0.5f,
            displaySize.X * 0.5f, displaySize.Y * 0.5f), holeAt, holeSize,
            new ColorPair(0x0033444Eu, 0xFFD5DEE4u), 64);

          canvas.Ribbon(holeAt, holeSize * 0.75f, holeSize, 64, new ColorPair(0x00D5DEE4u, 0xFFD5DEE4u),
            new ColorPair(0x00D5DEE4u, 0xFFD5DEE4u), new ColorPair(0x00D5DEE4u, 0xFFD5DEE4u));

          // Draw a vector-based path, rescaling and moving it where it should appear.
          float bufferScale = scale / 1.25f;

          RectF finalExtents = new(
            canvasBufferExtents.Left * bufferScale, canvasBufferExtents.Top * bufferScale,
            canvasBufferExtents.Width * bufferScale, canvasBufferExtents.Height * bufferScale);

          canvas.Attributes = Canvas.Attribute.Transform;
          canvas.Transform = Matrix3x2.CreateScale(bufferScale) * Matrix3x2.CreateTranslation(
            displaySize.X * 0.25f - (finalExtents.Left + finalExtents.Right) * 0.5f,
            displaySize.Y * 0.75f - (finalExtents.Top + finalExtents.Bottom) * 0.5f);

          canvas.Draw(canvasBuffer);

          canvas.Attributes = 0;
          canvas.SamplerState = new CanvasSamplerState(TextureFilter.Nearest, TextureFilter.Nearest);
          canvas.ContextState = CanvasContextState.FlatText;
          { // Draw text hints.
            FontParameters parameters = new("Cambria", 16.0f * scale, FontWeight.SemiBold);
            parameters.Effect.BorderType = FontBorder.Heavy;
            parameters.Effect.BorderBrightness = 1.0f;
            parameters.Effect.FillBrightness = 0.0f;
            parameters.Effect.ShadowBrightness = 0.75f;
            parameters.Effect.ShadowOpacity = 1.0f;
            textRenderer.Parameters = parameters;

            textRenderer.DrawAligned(new Vector2(
              displaySize.X * 0.5f, displaySize.Y * 0.25f - 75.0f * scale), "Arc",
              new ColorPair(0xFF3BFFE5u, 0xFF00FF00u), TextAlignment.Middle, TextAlignment.End);

            textRenderer.DrawAligned(new Vector2(
              displaySize.X * 0.2f, displaySize.Y * 0.25f - 75.0f * scale), "Hexagon",
              new ColorPair(0xFF5DD2FFu, 0xFF3600FFu), TextAlignment.Middle, TextAlignment.End);

            textRenderer.DrawAligned(new Vector2(
              displaySize.X * 0.8f, displaySize.Y * 0.25f - 75.0f * scale), "Tapes",
              new ColorPair(0xFFFFFAEAu, 0xFFFF427Bu), TextAlignment.Middle, TextAlignment.End);

            textRenderer.DrawAligned(new Vector2(
              displaySize.X * 0.25f, displaySize.Y * 0.75f - 110.0f * scale), "Path",
              new ColorPair(0xFF3BFFE5u, 0xFF00E0FFu), TextAlignment.Middle, TextAlignment.End);

            textRenderer.DrawAligned(new Vector2(
              displaySize.X * 0.75f, displaySize.Y * 0.75f - 110.0f * scale), "Rectangle with a hole",
              new ColorPair(0xFFB3F4FFu, 0xFF4C82A9u), TextAlignment.Middle, TextAlignment.End);
          }
          { // Draw technology string.
            FontParameters parameters = new("Segoe UI", 14.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.DrawAligned(new Vector2(displaySize.X * 0.5f, displaySize.Y -
              2.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.Middle, TextAlignment.End);
          }
        }
        finally
        {
          canvas.End();
        }
      }
      finally
      {
        swapChain.End();
      }
    }

    private void Application_Idle(object sender, EventArgs e)
    {
      if (WindowState != FormWindowState.Minimized)
        Invalidate(); // Continuously repaint the form.
    }

    private void CreatePathFillAndStroke()
    {
      PathBuilder builder = new();

      // Important notes: the resulting path must not self-intersect and the distance between each segment
      // must be bigger than the stroke thickness. Similarly, if there are any holes, these need to be
      // created using opposite winding (e.g. counter-clockwise vs clockwise) and must not self-intersect,
      // nor intersect the parent path.

      // Create a path using some Bezier curves.
      builder.MoveTo(
        new Vector2(0.0f, 0.0f));

      builder.CurveTo(
        new Vector2(-100.0f, 0.0f),
        new Vector2(100.0f, 300.0f),
        new Vector2(100.0f, 200.0f));

      builder.SmoothCurveTo(
        new Vector2(250.0f, 150.0f),
        new Vector2(250.0f, 50.0f));

      builder.SmoothCurveTo(
        new Vector2(100.0f, 0.0f),
        new Vector2(0.0f, 0.0f));

      builder.ClosePath();

      // Uncomment the following to create a small hole inside previous path.
/*
      builder.MoveTo(new Vector2(175.0f, 50.0f));
      builder.LineTo(new Vector2(75.0f, 100.0f));
      builder.LineTo(new Vector2(85.0f, 35.0f));
      builder.ClosePath();
*/
      // Convert this path into a buffer that can be drawn with canvas.
      canvasBuffer.Clear();

      // First, let's fill the path.
      pathBroker.Fill(canvasBuffer, builder.Elements,
        new ColorRect(0xFFFFFF00u, 0xFF00FFFFu, 0xFFFFFFFFu, 0xFFFF00FFu));

      // Second, let's draw some stroke on top of it.
      pathBroker.Stroke(canvasBuffer, builder.Elements, 10.0f, 0xFF202020u, PathJoint.Miter);

      // Calculate the resulting dimensions of the buffer.
      canvasBufferExtents = canvasBuffer.Extents;
    }
  }
}