/*
 * This file is part of Afterwarp Framework (http://afterwarp.io).
 * Copyright (c) 2015 - 2025 Dr. Yuriy Kotsarenko. All rights reserved.
 *
 * 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.

#ifdef _WIN32
  #include "Resource.h"
  #include <SDKDDKVer.h>

  #define WIN32_LEAN_AND_MEAN
  #include <windows.h>
  // Remove conflicting Windows macros.
  #undef KEY_EXECUTE
#endif

#include <stdio.h>

#include "Afterwarp.h"
#include "Afterwarp.Vectors.h"

#define AFTERWARP_VECTORS_IMPLEMENTATION
#include "Afterwarp.Vectors.h"

// Global variables.

// Display characteristics.
static Point displaySize = {1280, 720};
static float displayScale = 1.0f;

// Base Afterwarp interfaces.
static Application* application = NULL;
static Device* device = NULL;
static SwapChain* swapChain = NULL;
static Canvas* canvas = NULL;
static TextRenderer* textRenderer = NULL;
static Timer* timer = NULL;

static GaussianBlur* gaussianBlur = NULL;
static ColorDithering* colorDithering = NULL;

static Texture* textureCanvasMSAA = NULL;
static Texture* textureCanvas = NULL;
static Texture* textureBlurred = NULL;
static Texture* textureBlurredCopy = NULL;
static Texture* textureBlurredDithered = NULL;

// Forward declarations.

static bool createTextures(void);
static void destroyTextures(void);
static void outputError(char const* message);

// Implementation.

LibraryBool applicationCreate(Application* application, void* user)
{
  device = DeviceCreate(application, DEVICE_TECHNOLOGY_UNKNOWN, 0);
  if (!device)
  {
    outputError("Could not create graphics device.");
    return false;
  }
  swapChain = SwapChainCreate(device, ApplicationGetWindowHandle(application), &displaySize,
    PIXEL_FORMAT_RGBA8, PIXEL_FORMAT_UNKNOWN, 0, false);
  if (!swapChain)
  {
    outputError("Could not create rendering swap-chain.");
    return false;
  }
#ifndef _WIN32
  {
    char const* fileNames[] = {"../../../../Media/afterwarp.ico"};

    if (!ApplicationSetIconsFromFiles(application, fileNames, sizeof(fileNames) / sizeof(fileNames[0])))
    {
      outputError("Could not load application icon.");
      return false;
    }
  }
#endif
  canvas = CanvasCreate(device);
  if (!canvas)
  {
    outputError("Could not create rendering canvas.");
    return false;
  }
  textRenderer = TextRendererCreate(canvas, &(Point){512, 512}, PIXEL_FORMAT_LA8, false);
  if (!textRenderer)
  {
    outputError("Could not create text rendering module.");
    return false;
  }
  gaussianBlur = GaussianBlurCreate(device, 3.0f, 64, true);
  if (!gaussianBlur)
  {
    outputError("Could not create gaussian blur module.");
    return false;
  }
  colorDithering = ColorDitheringCreate(device);
  if (!colorDithering)
  {
    outputError("Could not create color dithering module.");
    return false;
  }
  timer = TimerCreate();
  if (!timer)
  {
    outputError("Could not create timer module.");
    return false;
  }
  if (!createTextures())
  {
    outputError("Could not create rendering textures.");
    return false;
  }
  return true;
}

void applicationDestroy(Application* application, void* user)
{
  destroyTextures();
  TimerDestroy(timer);
  ColorDitheringDestroy(colorDithering);
  GaussianBlurDestroy(gaussianBlur);
  TextRendererDestroy(textRenderer);
  CanvasDestroy(canvas);
  SwapChainDestroy(swapChain);
  DeviceDestroy(device);
}

void applicationResize(Application* application, void* user)
{
  Rect clientRect = {0};
  ApplicationGetClientRect(application, &clientRect);

  displaySize = rectGetSize(clientRect);

  SwapChainResize(swapChain, &displaySize);
}

void applicationRender(Application* application, void* user)
{
  displayScale = ApplicationGetWindowScale(application);
  double const ticks = GetSystemTicks() * 60.0 / 1000000.0;

  DeviceTechnology const technology = DeviceGetTechnology(device);

  bool const flipped = technology == DEVICE_TECHNOLOGY_OPENGL || technology == DEVICE_TECHNOLOGY_OPENGL_ES;
  Quad const texCoords = flipped ? quadFlip(quadUnity) : quadUnity;

  if (textureCanvasMSAA)
  {
    TextureClearWith(textureCanvasMSAA, &(FloatColor){1.0f, 1.0f, 1.0f, 1.0f}, 0, 0);

    if (TextureBegin(textureCanvasMSAA, 0, 0))
    {
      uint64_t prng;
      RandomSequenceInitBySeed(&prng, 1200);

      if (CanvasBegin(canvas))
      {
        CanvasSetSamplerState(canvas, &(CanvasSamplerState){TEXTURE_FILTER_NEAREST, TEXTURE_FILTER_NEAREST});
        CanvasSetContextState(canvas, CANVAS_CONTEXT_STATE_FLAT_SCENE);
        CanvasSetAttributes(canvas, CANVAS_ATTRIBUTE_TRANSFORM);

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

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

            Color const color = MakeColorRGB(
              RandomSequenceGenerateRanged(&prng, 256),
              RandomSequenceGenerateRanged(&prng, 256),
              RandomSequenceGenerateRanged(&prng, 256), 255);

            Matrix3x2 transform = matrixScale3x2((PointF){hexSize, hexSize});
            transform = matrixMultiply3x2(transform, matrixTranslate3x2((PointF){
              i * (interleave + hexSize * 2.0f) + offset,
              j * (interleave + (hexSize * 2.0f) / hexDelta)}));

            CanvasSetTransform(canvas, &transform);

            CanvasHexagon(canvas,
              AverageColors(color, 0x80FFFFFF),
              MultiplyColors(color, 0x80E0E0E0),
              MultiplyColors(color, 0x80D0D0D0),
              MultiplyColors(color, 0x80C0C0C0),
              MultiplyColors(color, 0x80909090),
              MultiplyColors(color, 0x80808080), BLENDING_EFFECT_NORMAL);
          }

        CanvasSetAttributes(canvas, 0);

        float kappa = (float)(ticks * 0.0274);

        CanvasArcGrad(canvas, &(PointF){256.0f, 128.0f}, &(PointF){64.0f, 64.0f}, kappa, kappa +
          (float)(1.25 * M_PI + sin(ticks * 0.01854) * 0.5 * M_PI), 32,
          &(ColorRect){0xFF00FF00u, 0xFFFF0000u, 0xFF0000FFu, 0xFFFFFFFFu}, BLENDING_EFFECT_COPY);

        kappa = (float)(ticks * 0.02231);

        CanvasTapeGrad(canvas, &(PointF){256.0f, 384.0f}, &(PointF){25.0f, 20.0f},
          &(PointF){70.0f, 80.0f}, kappa, kappa +
          (float)(1.25 * M_PI + sin(ticks * 0.024751) * 0.5 * M_PI), 128,
          &(ColorRect){0xFFFF0000u, 0xFF00FF00u, 0xFF0000FFu, 0xFFFFFFFFu}, BLENDING_EFFECT_COPY);

        CanvasSetContextState(canvas, CANVAS_CONTEXT_STATE_FLAT_TEXT);

        TextRendererSetFontParameters(textRenderer, &(FontParameters){
          .family = "Arial", .size = 16.0f, .weight = FONT_WEIGHT_SEMI_BOLD,
          .stretch = FONT_STRETCH_NORMAL, .effect = fontEffectDefault});

        TextRendererDrawAligned(textRenderer, &(PointF){256.0f, 510.0f}, "Some text on drawable texture.",
          &COLOR_PAIR(0xFF000000u), TEXT_ALIGNMENT_MIDDLE, TEXT_ALIGNMENT_END, 1.0f, true, NULL);

        TextRendererSetFontParameters(textRenderer, &(FontParameters){
          .family = "Verdana", .size = 24.0f, .weight = FONT_WEIGHT_BOLD,
          .stretch = FONT_STRETCH_NORMAL, .effect = fontEffectDefault});

        TextRendererDrawAligned(textRenderer, &(PointF){256.0f, 256.0f}, "Bold text for blurring purposes.",
          &COLOR_PAIR(0xFF000000u), TEXT_ALIGNMENT_MIDDLE, TEXT_ALIGNMENT_MIDDLE, 1.0f, true, NULL);

        CanvasEnd(canvas);
      }
      TextureEnd(textureCanvasMSAA);
    }
  }
  // Resolve MSAA texture.
  TextureCopy(textureCanvas, textureCanvasMSAA, 0, NULL, 0, NULL, 0, 0);

  // Perform the blur.
  GaussianBlurSetSigma(gaussianBlur, Lerp(0.001f, 20.0f, SineCycle((float)(ticks * 0.007))));
  GaussianBlurUpdate(gaussianBlur, textureBlurred, textureBlurredCopy, textureCanvas);

  // Perform color dithering to eliminate color banding that results from conversion to 8-bit channels.
  ColorDitheringSetFormat(colorDithering, COLOR_DITHERING_FORMAT_RGB8);
  ColorDitheringExecuteTo(colorDithering, textureBlurredDithered, textureBlurred);

  if (SwapChainBegin(swapChain))
  {
    FloatColor const clearColor = floatColor(0xFFE1E2E6u);
    DeviceClear(device, DEVICE_CLEAR_LAYER_COLOR, &clearColor, 1.0f, 0);

    if (CanvasBegin(canvas))
    {
      CanvasSetContextState(canvas, CANVAS_CONTEXT_STATE_FLAT_SCENE);

      // Draw resolved canvas texture.
      Quad quad = makeQuad(displaySize.x * 0.5f - 512.0f, displaySize.y * 0.5f - 256.0f, 512.0f, 512.0f);
      CanvasTexturedQuad(canvas, textureCanvas, &quad, &texCoords, &COLOR_RECT(0xFFFFFFFFu),
        BLENDING_EFFECT_COPY);

      // Draw blurred canvas texture.
      quad = makeQuad(displaySize.x * 0.5f, displaySize.y * 0.5f - 256.0f, 512.0f, 512.0f);
      CanvasTexturedQuad(canvas, textureBlurredDithered, &quad, &texCoords, &COLOR_RECT(0xFFFFFFFFu),
        BLENDING_EFFECT_COPY);

      // Prepare canvas for text output.
      CanvasSetContextState(canvas, CANVAS_CONTEXT_STATE_FLAT_TEXT);
      CanvasSetSamplerState(canvas, &(CanvasSamplerState){TEXTURE_FILTER_NEAREST, TEXTURE_FILTER_NEAREST});

      { // Draw technology string.
        FontParameters parameters = {
          .family = "Segoe UI", .size = 16.0f * displayScale, .weight = FONT_WEIGHT_SEMI_BOLD,
          .stretch = FONT_STRETCH_NORMAL, .slant = FONT_SLANT_NONE, .effect = fontEffectDefault};
        parameters.effect.borderType = FONT_BORDER_SEMI_HEAVY;
        parameters.effect.borderBrightness = 1.0f;
        parameters.effect.fillBrightness = 0.0f;
        parameters.effect.shadowBrightness = 0.75f;
        parameters.effect.shadowOpacity = 1.0f;
        TextRendererSetFontParameters(textRenderer, &parameters);

        char const* const textDirect3D = "Direct3D (%d.%d), features: %d.%d";
        char const* const textOpenGL = "OpenGL (%d.%d+), features: %d.%d";

        char const* const formatText = DeviceGetTechnology(device) == DEVICE_TECHNOLOGY_DIRECT3D ?
          textDirect3D : textOpenGL;

        char text[128];

        snprintf(text, sizeof(text), "Frame Rate: %1.0f", TimerGetFrameRate(timer));

        TextRendererDraw(textRenderer, &(PointF){8.0f * displayScale, 4.0f * displayScale}, text,
          &COLOR_PAIR(0xA0FFFFFFu), 1.0f, NULL);

        snprintf(text, sizeof(text), formatText,
          DeviceGetTechVersion(device) >> 8, (DeviceGetTechVersion(device) >> 4) & 0xF,
          DeviceGetTechFeatureVersion(device) >> 8, (DeviceGetTechFeatureVersion(device) >> 4) & 0xF);

        TextRendererDrawAligned(textRenderer, &(PointF){displaySize.x - 8.0f * displayScale,
          displaySize.y - 4.0f * displayScale}, text, &COLOR_PAIR(0xA0FFFFFFu), TEXT_ALIGNMENT_END,
          TEXT_ALIGNMENT_END, 1.0f, true, NULL);
      }
      CanvasEnd(canvas);
    }
    SwapChainEnd(swapChain);
  }
  TimerUpdate(timer);
}

LibraryBool applicationIdle(Application* application, void* user)
{
  ApplicationInvalidate(application);
  return false;
}

bool createTextures(void)
{
  TextureParameters parameters = {0};
  parameters.width = 512;
  parameters.height = 512;
  parameters.attributes = TEXTURE_ATTRIBUTE_DRAWABLE;
#if defined(_MSC_VER) && defined(_M_ARM64)
  // Use lower quality settings on mobile WoA devices.
  parameters.format = PIXEL_FORMAT_RGB10A2;
  parameters.multisamples = 4;
#else
  parameters.format = PIXEL_FORMAT_RGBA16;
  parameters.multisamples = 8;
#endif

  textureCanvasMSAA = TextureCreate(device, &parameters);

  parameters.multisamples = 0;

  textureCanvas = TextureCreate(device, &parameters);
  textureBlurred = TextureCreate(device, &parameters);
  textureBlurredCopy = TextureCreate(device, &parameters);

  parameters.format = PIXEL_FORMAT_RGBA8;

  textureBlurredDithered = TextureCreate(device, &parameters);

  return textureCanvasMSAA && textureCanvas && textureBlurred && textureBlurredCopy && textureBlurredDithered;
}

void destroyTextures(void)
{
  TextureDestroy(textureBlurredDithered);
  TextureDestroy(textureBlurredCopy);
  TextureDestroy(textureBlurred);
  TextureDestroy(textureCanvas);
  TextureDestroy(textureCanvasMSAA);
}

void outputError(char const* message)
{
#ifdef _WIN32
  static char const* initErrorTitle = "Initialization Error";
  MessageBoxA(NULL, message, initErrorTitle, MB_ICONERROR | MB_OK | MB_DEFAULT_DESKTOP_ONLY);
#else
  printf("%s\n", message);
#endif
}

// Main application entry.

#ifdef _WIN32

int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prevInstance, _In_ LPWSTR cmdLine,
  _In_ int showCmd)
{
  UNREFERENCED_PARAMETER(prevInstance);
  UNREFERENCED_PARAMETER(cmdLine);
  UNREFERENCED_PARAMETER(showCmd);

  char const* windowClassName = "AfterwarpDrawableTexturesDemo";

#else

int main(int argc, char **argv)
{

#endif

  ApplicationConfiguration configuration = {0};
  configuration.size = displaySize;
  configuration.position = (Point){.x = INT32_MAX, .y = INT32_MAX}; // default position
#ifdef _WIN32
  configuration.startup.win.handleInstance = instance;
  configuration.startup.win.handleIconSmall = LoadIcon(instance, MAKEINTRESOURCE(IDI_AFTERWARP));
  configuration.startup.win.handleIconBig = LoadIcon(instance, MAKEINTRESOURCE(IDI_AFTERWARP));

  strncpy(configuration.startup.win.windowClassName, windowClassName,
    sizeof(configuration.startup.win.windowClassName));
#else
  configuration.startup.nx.parameterCount = argc;
  configuration.startup.nx.parameterStrings = argv;
#endif

  application = ApplicationCreate(&configuration);
  if (!application)
  {
    outputError("Could not create Application object.");
    return -1;
  }

  ApplicationEvents events = {0};
  events.eventCreate = applicationCreate;
  events.eventDestroy = applicationDestroy;
  events.eventResize = applicationResize;
  events.eventRender = applicationRender;
  events.eventIdle = applicationIdle;
  ApplicationSetEvents(application, &events, NULL);

  displayScale = ApplicationGetWindowScale(application);
  ApplicationSetTitle(application, "Afterwarp: DrawableTextures example");

  { // Retrieve client size back in case it has changed since startup.
    Rect clientRect = {0};
    ApplicationGetClientRect(application, &clientRect);

    displaySize = rectGetSize(clientRect);
  }

  bool const executed = ApplicationExecute(application);
  ApplicationDestroy(application);

  return executed ? 0 : -1;
}