/*
 * xrdesktop
 * Copyright 2019 Collabora Ltd.
 * Author: Christoph Haag <christoph.haag@collabora.com>
 * Author: Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
 * SPDX-License-Identifier: MIT
 */

#include <glib.h>
#include "xrd.h"

/* access to static functions */
#include "../src/xrd-client.c"
#include "../src/scene/xrd-scene-client.c"

static GdkPixbuf *
_load_gdk_pixbuf (const gchar* name, int w)
{
  GError * error = NULL;
  GdkPixbuf *pixbuf_rgb = gdk_pixbuf_new_from_resource (name, &error);

  if (error != NULL)
    {
      g_printerr ("Unable to read file: %s\n", error->message);
      g_error_free (error);
      return NULL;
    }

  GdkPixbuf *pixbuf = gdk_pixbuf_add_alpha (pixbuf_rgb, FALSE, 0, 0, 0);
  g_object_unref (pixbuf_rgb);

  int width = gdk_pixbuf_get_width (pixbuf);
  int height = gdk_pixbuf_get_height (pixbuf);
  float aspect = (float)width / (float) height;

  int h = (int)((float) w / aspect);
  GdkPixbuf *pixbuf_scaled =
    gdk_pixbuf_scale_simple (pixbuf, w, h, GDK_INTERP_NEAREST);
  g_object_unref (pixbuf);

  return pixbuf_scaled;
}

static GulkanTexture *
_make_texture (GulkanClient *gc, const gchar *resource, int w)
{
  GdkPixbuf *pixbuf = _load_gdk_pixbuf (resource, w);
  if (pixbuf == NULL)
    {
      g_printerr ("Could not load image.\n");
      return FALSE;
    }

  GulkanTexture *texture =
    gulkan_texture_new_from_pixbuf (gc, pixbuf,
                                    VK_FORMAT_R8G8B8A8_UNORM,
                                    VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
                                    TRUE);

  g_object_unref (pixbuf);

  return texture;
}

static gboolean
_similar (float a, float b)
{
  return fabs ((double)a - (double)b) < 0.01;
}

static VkExtent2D extent;

static float expected_mouse_x = 0;
static float expected_mouse_y = 0;
static gboolean success = FALSE;
static void
_move_cursor_cb (XrdClient          *client,
                 XrdMoveCursorEvent *event,
                 gpointer           *_)
{
  (void) client;
  (void) _;
  (void) event;

  float x = event->position->x;
  float y = (float) extent.height - event->position->y;

  g_print ("move: %f, %f\n", (double)x, (double)y);
  if (_similar (expected_mouse_x, x) || !_similar (expected_mouse_y, y))
    success = TRUE;
  else
    {
      g_print ("Error: mouse move to unexpected location!\n");
      g_print ("IS    : %f, %f\n", (double)event->position->x, (double)y);
      g_print ("SHOULD: %f %f\n",
               (double)expected_mouse_x, (double)expected_mouse_y);
      success = FALSE;
    }
}

static void
_cleanup (XrdClient *client)
{
  g_object_unref (client);
}

static gboolean
_test_move (float left, float bottom, float ppm, float dist, XrdClient *client,
            int x, int y)
{
  g_print ("Test %d %d\n", x, y);
  graphene_vec3_t eye;
  graphene_vec3_init (&eye, 0, 0, 0);

  graphene_vec3_t to;
  graphene_vec3_init (&to,
                      left + (float)x / ppm,
                      bottom + (float)y / ppm,
                      dist);

  graphene_matrix_t pose;
  graphene_matrix_init_look_at (&pose, &eye, &to, graphene_vec3_y_axis());

  expected_mouse_x = (float) x;
  expected_mouse_y = (float) y;

  XrdClientPrivate *priv = xrd_client_get_instance_private (client);
  GxrController *controller = gxr_controller_new (1, priv->context, "");
  _init_scene_controller (client, controller);
  xrd_input_synth_make_primary (priv->input_synth, controller);

  gxr_controller_update_pointer_pose (controller, &pose, TRUE);
  xrd_window_manager_update_controller (priv->manager, controller);

  g_object_unref (controller);

  return success;
}

static int
_test_scene_client ()
{
  /* _create_gxr_context() imported from xrd-client.c */
  GxrContext *context = _create_gxr_context (GXR_APP_SCENE);
  if (!context)
    {
      g_error ("Could not init VR runtime.\n");
      return 1;
    }

  XrdSceneClient *client = xrd_scene_client_new (context);
  g_assert_nonnull (client);

  g_signal_connect (client, "move-cursor-event",
                    (GCallback) _move_cursor_cb, client);

  GulkanClient *gc = xrd_client_get_gulkan (XRD_CLIENT (client));
  GulkanTexture *texture = _make_texture (gc, "/res/cat.jpg", 800);
  if (!texture)
    return 1;

  extent = gulkan_texture_get_extent (texture);
  float aspect = (float)extent.width / (float)extent.height;

  float window_width_meter = 2.0;
  float window_height_meter = window_width_meter / aspect;
  float ppm = (float) extent.width / window_width_meter;

  XrdWindow *window =
    xrd_client_window_new_from_meters (XRD_CLIENT (client), "win.",
                                       window_width_meter,
                                       window_height_meter, ppm);

  float left = 0.5f;
  float right = left + window_width_meter;
  float bottom = 1.5f;
  float top = bottom + window_height_meter;

  float dist = -3.f;

  graphene_point3d_t point = {
    .x = left + (right - left) / 2.f,
    .y = bottom + (top - bottom) / 2.f,
    .z = dist
  };
  graphene_matrix_t transform;
  graphene_matrix_init_translate (&transform, &point);
  xrd_window_set_transformation (XRD_WINDOW (window), &transform);

  xrd_window_set_and_submit_texture (XRD_WINDOW (window), texture);

  xrd_client_add_window (XRD_CLIENT (client), XRD_WINDOW (window), TRUE, NULL);

  GulkanDevice *device = gulkan_client_get_device (gc);
  gulkan_device_wait_idle (device);

  xrd_scene_client_render (client);
  gulkan_device_wait_idle (device);

  xrd_scene_client_render (client);
  gulkan_device_wait_idle (device);

  const int ms_delay = 150;

  gboolean succ =
    _test_move (left, bottom, ppm, dist, XRD_CLIENT (client),
                100, 100);
  if (succ != TRUE)
    {
      _cleanup (XRD_CLIENT (client));
      return 1;
    }
  for (int i = 0; i < ms_delay; i+= 10)
    {
      xrd_scene_client_render (client);
      gulkan_device_wait_idle (device);
      g_usleep (ms_delay / 10);
    }

  succ =
    _test_move (left, bottom, ppm, dist, XRD_CLIENT (client),
                 10, 300);
  if (succ != TRUE)
    {
      _cleanup (XRD_CLIENT (client));
      return 1;
    }
  for (int i = 0; i < ms_delay; i+= 10)
    {
      xrd_scene_client_render (client);
      gulkan_device_wait_idle (device);
      g_usleep (ms_delay / 10);
    }

  succ =
    _test_move (left, bottom, ppm, dist, XRD_CLIENT (client),
                 400, 10);
  if (succ != TRUE)
    {
      _cleanup (XRD_CLIENT (client));
      return 1;
    }
  for (int i = 0; i < ms_delay; i+= 10)
    {
      xrd_scene_client_render (client);
      gulkan_device_wait_idle (device);
      g_usleep (ms_delay / 10);
    }

  _cleanup (XRD_CLIENT (client));

  return 0;
}

int
main ()
{
  return _test_scene_client ();
}
