/*
 * Copyright (C) 2020 Purism SPC
 *               2025 Phosh.mobi e.V.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
#include "server.h"

#include "gtk-shell-client-protocol.h"
#include "phoc-layer-shell-effects-unstable-v1-client-protocol.h"
#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "wlr-screencopy-unstable-v1-client-protocol.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
#include "xx-cutouts-v1-client-protocol.h"

/* Needs wlr-screencopy-unstable */
#include "phosh-private-client-protocol.h"

#include <glib.h>

#pragma once

G_BEGIN_DECLS

typedef struct _PhocTestClientGlobals PhocTestClientGlobals;

typedef struct _PhocTestBuffer {
  struct wl_buffer *wl_buffer;
  guint8 *shm_data;
  guint32 width, height, stride;
  enum wl_shm_format format;
  gboolean valid;
} PhocTestBuffer;


typedef struct _PhocTestScreencopyFrame {
  PhocTestBuffer buffer;
  gboolean done;
  uint32_t flags;
  PhocTestClientGlobals *globals;
} PhocTestScreencopyFrame;


typedef struct _PhocTestOutputConfig {
  guint width;
  guint height;
  float scale;
  enum wl_output_transform transform;
} PhocTestOutputConfig;


typedef struct _PhocTestOutput {
  struct wl_output *output;
  guint32 width, height;
  PhocTestScreencopyFrame screenshot;
} PhocTestOutput;


struct _PhocTestClientGlobals {
  struct wl_display                       *display;
  struct wl_compositor                    *compositor;
  struct wl_shm                           *shm;
  struct xdg_wm_base                      *xdg_shell;
  struct xx_cutouts_manager_v1            *cutouts_manager;
  struct zwlr_layer_shell_v1              *layer_shell;
  struct zphoc_layer_shell_effects_v1     *layer_shell_effects;
  struct zwlr_screencopy_manager_v1       *screencopy_manager;
  struct zwlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager;
  struct zxdg_decoration_manager_v1       *decoration_manager;
  GSList *foreign_toplevels;
  struct phosh_private                    *phosh;
  struct gtk_shell1                       *gtk_shell1;
  /* TODO: handle multiple outputs */
  PhocTestOutput                           output;
  PhocTestOutputConfig                     output_config;

  GPtrArray *formats;
};


typedef struct _PhocTestForeignToplevel {
  char* title;
  struct zwlr_foreign_toplevel_handle_v1 *handle;
  PhocTestClientGlobals *globals;
} PhocTestForeignToplevel;


typedef gboolean (* PhocTestServerFunc) (PhocServer *server, gpointer data);
typedef gboolean (* PhocTestClientFunc) (PhocTestClientGlobals *globals, gpointer data);


typedef struct PhocTestClientIface {
  /* Prepare function runs in server context */
  PhocTestServerFunc   server_prepare;
  PhocTestClientFunc   client_run;
  PhocServerFlags      server_flags;
  PhocServerDebugFlags debug_flags;
  gboolean xwayland;
  PhocTestOutputConfig output_config;
  const char * const  *compatibles;
} PhocTestClientIface;

typedef struct _PhocTestXdgToplevelSurface PhocTestXdgToplevelSurface;
typedef void (* PhocXdgAckConfigureCallback) (PhocTestXdgToplevelSurface *surface, gpointer data);

struct _PhocTestXdgToplevelSurface {
  struct wl_surface *wl_surface;
  struct xdg_surface *xdg_surface;
  struct xdg_toplevel *xdg_toplevel;
  PhocTestForeignToplevel *foreign_toplevel;
  char* title;
  PhocTestBuffer buffer;
  guint32 width, height;
  gboolean configured;
  gboolean toplevel_configured;
  struct {
    PhocXdgAckConfigureCallback callback;
    gpointer data;
    GDestroyNotify data_free_func;
  } ack_configure;
};


typedef struct _PhocTestFixture {
  GTestDBus   *bus;
  char        *tmpdir;
} PhocTestFixture;

/* Test client */
void phoc_test_client_run (int timeout, PhocTestClientIface *iface, gpointer data);
int  phoc_test_client_create_shm_buffer (PhocTestClientGlobals *globals,
                                         PhocTestBuffer *buffer,
                                         int width, int height, guint32 format);
PhocTestBuffer *phoc_test_client_capture_frame (PhocTestClientGlobals *globals,
                                                PhocTestScreencopyFrame *frame,
                                                struct zwlr_screencopy_frame_v1 *handle);
PhocTestBuffer *phoc_test_client_capture_output (PhocTestClientGlobals *globals,
                                                 PhocTestOutput *output);
PhocTestForeignToplevel *phoc_test_client_get_foreign_toplevel_handle (PhocTestClientGlobals *globals,
                                                                       const char *title);
/* Test surfaces */
PhocTestXdgToplevelSurface *
                phoc_test_xdg_toplevel_new (PhocTestClientGlobals *globals,
                                            guint32                width,
                                            guint32                height,
                                            const char            *title);
PhocTestXdgToplevelSurface *
                phoc_test_xdg_toplevel_new_with_buffer (PhocTestClientGlobals     *globals,
                                                        guint32                    width,
                                                        guint32                    height,
                                                        const char                *title,
                                                        guint32                    color);
void            phoc_test_xdg_toplevel_free (PhocTestXdgToplevelSurface *xs);
void            phoc_test_xdg_toplevel_set_ack_configure_callback (PhocTestXdgToplevelSurface  *xs,
                                                                   PhocXdgAckConfigureCallback  callback,
                                                                   gpointer                     data,
                                                                   GDestroyNotify               data_free_func);
void            phoc_test_xdg_update_buffer (PhocTestClientGlobals      *globals,
                                             PhocTestXdgToplevelSurface *xs,
                                             guint32                     color);
/* Buffers */
gboolean phoc_test_buffer_equal (PhocTestBuffer *buf1, PhocTestBuffer *buf2);
gboolean phoc_test_buffer_save (PhocTestBuffer *buffer, const char *filename);
gboolean phoc_test_buffer_matches_screenshot (PhocTestBuffer *buffer, const char *filename);
void     phoc_test_buffer_free (PhocTestBuffer *buffer);

#define _phoc_test_screenshot_name(l, f, n) \
  (g_strdup_printf ("phoc-test-screenshot-%d-%s_%d.png", l, f, n))

/*
 * phoc_assert_screenshot:
 * @g: The client global object
 * @f: The screenshot to compare the current output to
 */
#define phoc_assert_screenshot(g, f) G_STMT_START {                                         \
    PhocTestClientGlobals *__g = (g);                                                       \
    char *__f = g_test_build_filename (G_TEST_DIST, "screenshots", f, NULL);                \
    PhocTestBuffer *__s = phoc_test_client_capture_output (__g, &__g->output);              \
    g_test_message ("Snapshotting %s", f);                                                  \
    if (phoc_test_buffer_matches_screenshot (__s, __f)); else {                             \
      g_autofree char *__name = _phoc_test_screenshot_name (__LINE__, G_STRFUNC, 0);        \
      g_autofree char *__msg =                                                              \
        g_strdup_printf ("Output content in '%s' does not match " #f, __name);              \
      phoc_test_buffer_save (&__g->output.screenshot.buffer, __name);                       \
      g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, __msg);             \
    }                                                                                       \
    phoc_test_buffer_free (__s);                                                            \
    g_free (__f);                                                                           \
} G_STMT_END

/**
 * phoc_test_assert_buffer_equal:
 * @b1: A PhocClientBuffer
 * @b2: A PhocClientBuffer
 *
 * Debugging macro to compare two buffers. If the buffers don't match
 * screenshots are taken and saved as PNG.
 */
#define phoc_assert_buffer_equal(b1, b2)    G_STMT_START {                                  \
    PhocTestBuffer *__b1 = (b1), *__b2 = (b2);                                              \
    if (phoc_test_buffer_equal (__b1, __b2)); else {                                        \
      g_autofree char *__name1 = _phoc_test_screenshot_name (__LINE__, G_STRFUNC, 1);       \
      g_autofree char *__name2 = _phoc_test_screenshot_name (__LINE__, G_STRFUNC, 2);       \
      phoc_test_buffer_save (__b1, __name1);                                                \
      phoc_test_buffer_save (__b2, __name2);                                                \
      g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC,                     \
                           "Buffer " #b1 " != " #b2);                                       \
    }                                                                                       \
} G_STMT_END

/**
 * phoc_test_assert_cmpbox
 * @b1: A box
 * @b2: A box
 *
 * Debugging macro to compare two boxes.
 */
#define phoc_assert_cmpbox(b1, b2) G_STMT_START {                                           \
    __auto_type __b1 = (b1);                                                                \
    __auto_type __b2 = (b2);                                                                \
    if (__b1->x != __b2->x || __b1->y != __b2->y ||                                         \
        __b1->width != __b2->width || __b1->height != __b2->height) {                       \
      g_autofree char *__msg =                                                              \
        g_strdup_printf ("Box " #b1 " (%d,%d %dx%d) doesn't match " #b2 " (%d,%d %dx%d)",   \
                         __b1->x, __b1->y, __b1->width, __b1->height,                       \
                         __b2->x, __b2->y, __b2->width, __b2->height);                      \
      g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, __msg);             \
    }                                                                                       \
} G_STMT_END

/**
 * phoc_test_assert_cmpcutoutcorner
 * @b1: A box
 * @b2: A box
 *
 * Debugging macro to compare two boxes.
 */
#define phoc_assert_cmpcorner(c1, c2) G_STMT_START {                                        \
    __auto_type __c1 = (c1);                                                                \
    __auto_type __c2 = (c2);                                                                \
    if (__c1->position != __c2->position || __c1->radius != __c2->radius) {                 \
      g_autofree char *__msg =                                                              \
        g_strdup_printf ("Corner " #c1 " pos: %d, radius %d doesn't match " #c2             \
                         " (pos: %d, radius %d)",                                           \
                         __c1->position, __c1->radius,                                      \
                         __c2->position, __c2->radius);                                     \
      g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, __msg);             \
    }                                                                                       \
} G_STMT_END

/* Test setup and fixtures */
void phoc_test_setup (PhocTestFixture *fixture, gconstpointer data);
void phoc_test_teardown (PhocTestFixture *fixture, gconstpointer unused);
#define PHOC_TEST_ADD(name, func) g_test_add ((name), PhocTestFixture, NULL, \
                                              (gpointer)phoc_test_setup,     \
                                              (gpointer)(func),              \
                                              (gpointer)phoc_test_teardown)

G_END_DECLS
