/*
 * Copyright (C) 2026  Behdad Esfahbod
 *
 *  This is part of HarfBuzz, a text shaping library.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 * Author(s): Behdad Esfahbod
 */

#ifndef GPU_OUTPUT_HH
#define GPU_OUTPUT_HH

#include "gpu/demo-common.h"
#include "gpu/demo-buffer.h"
#include "gpu/demo-font.h"
#include "gpu/demo-renderer.h"
#include "gpu/demo-view.h"

#include "options.hh"
#include "font-options.hh"

#define WINDOW_W 700
#define WINDOW_H 700

struct gpu_output_t
{
  static constexpr bool repeat_shape = false;

  hb_bool_t use_metal = false;
  hb_bool_t use_d3d11 = false;
  hb_bool_t demo = false;
  hb_bool_t bench = false;
  char *type_text = nullptr;

  static gboolean
  parse_bench (const char *name G_GNUC_UNUSED,
	       const char *arg G_GNUC_UNUSED,
	       gpointer    data,
	       GError    **error G_GNUC_UNUSED)
  {
    auto *self = (gpu_output_t *) data;
    self->demo = true;
    self->bench = true;
    if (!self->type_text)
      self->type_text = g_strdup ("v30=f ");
    return true;
  }

  void add_options (option_parser_t *parser)
  {
    GOptionEntry entries[] =
    {
      {"demo",		0, 0, G_OPTION_ARG_NONE,	&this->demo,		"Use built-in demo font and text",	nullptr},
      {"bench",		0, G_OPTION_FLAG_NO_ARG,
				G_OPTION_ARG_CALLBACK,	(gpointer) &parse_bench,"Demo in fullscreen benchmark mode",	nullptr},
      {"type",		'T', 0, G_OPTION_ARG_STRING,	&this->type_text,	"Type these keystrokes on start",	"keys"},
#ifdef __APPLE__
      {"metal",		0, 0, G_OPTION_ARG_NONE,	&this->use_metal,	"Use Metal renderer",			nullptr},
#endif
#ifdef _WIN32
      {"direct3d",	0, 0, G_OPTION_ARG_NONE,	&this->use_d3d11,	"Use Direct3D 11 renderer",		nullptr},
#endif
      {nullptr}
    };
    parser->add_group (entries,
		       "gpu",
		       "GPU options:",
		       "Options for GPU rendering",
		       this,
		       false);
  }

  template <typename app_t>
  void init (hb_buffer_t *buffer_ HB_UNUSED, const app_t *app)
  {
    font = app->font;
    face = hb_font_get_face (font);

    int x_scale, y_scale;
    hb_font_get_scale (font, &x_scale, &y_scale);
    font_size = (double) y_scale;

    glfwSetErrorCallback ([] (int error, const char *desc) {
      fprintf (stderr, "GLFW error %d: %s\n", error, desc);
    });
    if (!glfwInit ())
      fail (false, "Failed to initialize GLFW");

    if (use_metal)
      init_metal ();
#ifdef _WIN32
    else if (use_d3d11)
      init_d3d11 ();
#endif
    else
      init_gl ();

    vu = demo_view_create (renderer, window);

    glfwSetWindowUserPointer (window, this);
    glfwSetFramebufferSizeCallback (window, [] (GLFWwindow *w, int width, int height) {
      auto *self = (gpu_output_t *) glfwGetWindowUserPointer (w);
      demo_view_reshape_func (self->vu, width, height);
    });
    glfwSetKeyCallback (window, [] (GLFWwindow *w, int key, int scancode, int action, int mods) {
      auto *self = (gpu_output_t *) glfwGetWindowUserPointer (w);
      demo_view_key_func (self->vu, key, scancode, action, mods);
    });
    glfwSetCharCallback (window, [] (GLFWwindow *w, unsigned int cp) {
      auto *self = (gpu_output_t *) glfwGetWindowUserPointer (w);
      demo_view_char_func (self->vu, cp);
    });
    glfwSetMouseButtonCallback (window, [] (GLFWwindow *w, int button, int action, int mods) {
      auto *self = (gpu_output_t *) glfwGetWindowUserPointer (w);
      demo_view_mouse_func (self->vu, button, action, mods);
    });
    glfwSetScrollCallback (window, [] (GLFWwindow *w, double xoff, double yoff) {
      auto *self = (gpu_output_t *) glfwGetWindowUserPointer (w);
      demo_view_scroll_func (self->vu, xoff, yoff);
    });
    glfwSetCursorPosCallback (window, [] (GLFWwindow *w, double x, double y) {
      auto *self = (gpu_output_t *) glfwGetWindowUserPointer (w);
      demo_view_motion_func (self->vu, x, y);
    });

    demo_font_ = demo_font_create (font, renderer->get_atlas ());
    buf = demo_buffer_create ();

    demo_point_t top_left = {0, 0};
    demo_buffer_move_to (buf, &top_left);
    demo_buffer_current_line (buf, font_size);
  }

  void init_gl ()
  {
    glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
    glfwWindowHint (GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
    glfwWindowHint (GLFW_SRGB_CAPABLE, GLFW_TRUE);

    window = glfwCreateWindow (WINDOW_W, WINDOW_H, "HarfBuzz GPU", NULL, NULL);
    if (!window) {
      glfwTerminate ();
      fail (false, "Failed to create GLFW window");
    }
    glfwMakeContextCurrent (window);

    glewExperimental = GL_TRUE;
    glewInit ();
    while (glGetError () != GL_NO_ERROR)
      ;
    if (!glewIsSupported ("GL_VERSION_3_3"))
      fail (false, "OpenGL 3.3 not supported");

    {
      int fb_width, fb_height;
      glfwGetFramebufferSize (window, &fb_width, &fb_height);
      glViewport (0, 0, fb_width, fb_height);
    }

    renderer = demo_renderer_create_gl (window);
  }

  void init_metal ()
  {
#ifdef __APPLE__
    glfwWindowHint (GLFW_CLIENT_API, GLFW_NO_API);

    window = glfwCreateWindow (WINDOW_W, WINDOW_H, "HarfBuzz GPU (Metal)", NULL, NULL);
    if (!window) {
      glfwTerminate ();
      fail (false, "Failed to create GLFW window");
    }

    renderer = demo_renderer_create_metal (window);
    if (!renderer) {
      glfwTerminate ();
      fail (false, "Failed to initialize Metal");
    }
#else
    fail (false, "Metal is only available on macOS");
#endif
  }

  void init_d3d11 ()
  {
#ifdef _WIN32
    glfwWindowHint (GLFW_CLIENT_API, GLFW_NO_API);

    window = glfwCreateWindow (WINDOW_W, WINDOW_H, "HarfBuzz GPU (Direct3D)", NULL, NULL);
    if (!window) {
      glfwTerminate ();
      fail (false, "Failed to create GLFW window");
    }

    renderer = demo_renderer_create_d3d11 (window);
    if (!renderer) {
      glfwTerminate ();
      fail (false, "Failed to initialize Direct3D 11");
    }
#else
    fail (false, "Direct3D is only available on Windows");
#endif
  }

  void new_line ()
  {
    demo_buffer_current_line (buf, font_size);
  }

  void consume_text (hb_buffer_t *buffer HB_UNUSED,
		     const char *text HB_UNUSED,
		     unsigned text_len HB_UNUSED,
		     hb_bool_t utf8_clusters HB_UNUSED) {}

  void consume_glyphs (hb_buffer_t *buffer,
		       const char *text HB_UNUSED,
		       unsigned text_len HB_UNUSED,
		       hb_bool_t utf8_clusters HB_UNUSED)
  {
    unsigned count = hb_buffer_get_length (buffer);
    hb_glyph_info_t *infos = hb_buffer_get_glyph_infos (buffer, nullptr);
    hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, nullptr);

    for (unsigned i = 0; i < count; i++)
      demo_buffer_add_glyph (buf, demo_font_,
			     font_size,
			     infos[i].codepoint,
			     pos[i].x_offset,
			     pos[i].y_offset,
			     pos[i].x_advance);
  }

  void error (const char *message)
  {
    fprintf (stderr, "error: %s\n", message);
  }

  template <typename app_t>
  void finish (hb_buffer_t *buffer_ HB_UNUSED, const app_t *app HB_UNUSED)
  {
    demo_font_print_stats (demo_font_);
    demo_view_print_help (vu);
    demo_view_setup (vu);

    if (type_text)
      demo_view_type (vu, type_text);
    if (bench)
      demo_view_set_fps_quit (vu, 3);

    demo_view_display (vu, buf);
    glfwPollEvents ();
    demo_view_display (vu, buf);

    while (!glfwWindowShouldClose (window))
    {
      glfwPollEvents ();
      if (demo_view_should_redraw (vu))
	demo_view_display (vu, buf);
      else
	glfwWaitEvents ();
    }

    demo_buffer_destroy (buf);
    demo_font_destroy (demo_font_);
    demo_view_destroy (vu);
    delete renderer;

    glfwDestroyWindow (window);
    glfwTerminate ();
  }

  private:

  double font_size = 1;
  hb_font_t *font = nullptr;
  hb_face_t *face = nullptr;

  GLFWwindow *window = nullptr;
  demo_renderer_t *renderer = nullptr;
  demo_view_t *vu = nullptr;
  demo_font_t *demo_font_ = nullptr;
  demo_buffer_t *buf = nullptr;
};

#endif /* GPU_OUTPUT_HH */
