/*
 * Copyright (C) 2026 Phosh.mobi e.V.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * Author: Guido Günther <agx@sigxcpu.org>
 */

#define G_LOG_DOMAIN "livi-cutouts"

#include "livi-application.h"
#include "livi-cutouts.h"

#include <gdk/gdk.h>
#include <gdk/wayland/gdkwayland.h>

#include <gtk/gtk.h>

/**
 * LiviCutouts:
 *
 * Cutouts of a GtkWindow
 */

enum {
  PROP_0,
  PROP_WINDOW,
  PROP_CUTOUTS,
  PROP_CORNERS,
  PROP_LAST_PROP
};
static GParamSpec *props[PROP_LAST_PROP];

enum {
  UPDATED,
  N_SIGNALS
};
static guint signals[N_SIGNALS];

struct _LiviCutouts {
  GObject parent;

  struct {
    GArray *corners;
    GArray *cutouts;
  } pending;

  struct {
    GArray *corners;
    GArray *cutouts;
  } current;

  GtkWindow *window;
  struct xx_cutouts_v1 *xx_cutouts;
};

G_DEFINE_TYPE (LiviCutouts, livi_cutouts, G_TYPE_OBJECT)


static void
handle_cutout_configure (void *data, struct xx_cutouts_v1 *xx_cutouts_v1)
{
  LiviCutouts *self = data;
  gboolean update = FALSE;

  if (self->current.corners->len != 0 || self->pending.corners->len != 0) {
    g_clear_pointer (&self->current.corners, g_array_unref);
    self->current.corners = g_array_copy (self->pending.corners);
    g_array_remove_range (self->pending.corners, 0, self->pending.corners->len);

    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CORNERS]);
    update = TRUE;
  }

  if (self->current.cutouts->len != 0 || self->pending.cutouts->len != 0) {
    g_clear_pointer (&self->current.cutouts, g_array_unref);
    self->current.cutouts = g_array_copy (self->pending.cutouts);
    g_array_remove_range (self->pending.cutouts, 0, self->pending.cutouts->len);

    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CUTOUTS]);
    update = TRUE;
  }

  if (update)
    g_signal_emit (self, signals[UPDATED], 0);
}


static void
handle_cutout_box (void                 *data,
                   struct xx_cutouts_v1 *xx_cutouts_v1,
                   int32_t               x,
                   int32_t               y,
                   int32_t               width,
                   int32_t               height,
                   uint32_t              type,
                   uint32_t              id)
{
  LiviCutouts *self = data;
  Cutout cutout;
  double x_off, y_off;

  gtk_native_get_surface_transform (GTK_NATIVE (self->window), &x_off, &y_off);

  cutout.box.x = x - x_off;
  cutout.box.y = y - y_off;
  cutout.box.width = width;
  cutout.box.height = height;
  cutout.id = id;

  g_array_append_val (self->pending.cutouts, cutout);
}


static void
handle_cutout_corner (void                 *data,
                      struct xx_cutouts_v1 *xx_cutouts_v1,
                      uint32_t              position,
                      uint32_t              radius,
                      uint32_t              id)
{
  LiviCutouts *self = data;
  Corner corner = { .radius = radius, .pos = position };

  g_array_append_val (self->pending.corners, corner);
}


const struct xx_cutouts_v1_listener cutouts_listener = {
  .cutout_box = handle_cutout_box,
  .cutout_corner = handle_cutout_corner,
  .configure = handle_cutout_configure,
};


static void
livi_cutouts_connect_listener (LiviCutouts *self)
{
  GdkSurface *gdk_surface;
  struct wl_surface *wl_surface;
  struct xx_cutouts_manager_v1 *cutouts_manager;
  LiviApplication *app = LIVI_APPLICATION (g_application_get_default ());

  g_return_if_fail (LIVI_IS_CUTOUTS (self));
  g_return_if_fail (self->window);

  if (self->xx_cutouts)
    return;

  gdk_surface = gtk_native_get_surface (GTK_NATIVE (self->window));
  if (!gdk_surface)
    return;

  wl_surface = gdk_wayland_surface_get_wl_surface (gdk_surface);
  if (!wl_surface)
    return;

  cutouts_manager = livi_application_get_xx_cutouts_manager_v1 (app);
  if (!cutouts_manager)
    return;

  g_debug ("Handling cutouts now.");
  self->xx_cutouts = xx_cutouts_manager_v1_get_cutouts (cutouts_manager, wl_surface);
  xx_cutouts_v1_add_listener (self->xx_cutouts, &cutouts_listener, self);
}


static void
on_window_realize (LiviCutouts *self)
{
  GApplication *app = g_application_get_default ();

  g_signal_connect_object (app,
                           "new-global",
                           G_CALLBACK (livi_cutouts_connect_listener),
                           self,
                           G_CONNECT_SWAPPED);

  livi_cutouts_connect_listener (self);
}


static void
livi_cutouts_set_window (LiviCutouts *self, GtkWindow *window)
{
  if (self->window == window)
    return;

  g_clear_pointer (&self->xx_cutouts, xx_cutouts_v1_destroy);
  g_clear_weak_pointer (&self->window);

  if (window == NULL)
    return;

  g_set_weak_pointer (&self->window, window);

  if (gtk_widget_get_realized (GTK_WIDGET (self->window))) {
    livi_cutouts_connect_listener (self);
  } else {
    /* wl_surface not yet available, wait until window is realized */
    g_signal_connect_object (self->window,
                             "realize",
                             G_CALLBACK (on_window_realize),
                             self,
                             G_CONNECT_SWAPPED);
  }
}


static void
livi_cutouts_set_property (GObject      *object,
                           guint         property_id,
                           const GValue *value,
                           GParamSpec   *pspec)
{
  LiviCutouts *self = LIVI_CUTOUTS (object);

  switch (property_id) {
  case PROP_WINDOW:
    livi_cutouts_set_window (self, g_value_get_object (value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}


static void
livi_cutouts_get_property (GObject    *object,
                           guint       property_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
  LiviCutouts *self = LIVI_CUTOUTS (object);

  switch (property_id) {
  case PROP_CUTOUTS:
    g_value_set_boxed (value, self->current.cutouts);
    break;
  case PROP_CORNERS:
    g_value_set_boxed (value, self->current.corners);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}


static void
livi_cutouts_dispose (GObject *object)
{
  LiviCutouts *self = LIVI_CUTOUTS (object);

  livi_cutouts_set_window (self, NULL);

  g_clear_pointer (&self->pending.corners, g_array_unref);
  g_clear_pointer (&self->pending.cutouts, g_array_unref);

  g_clear_pointer (&self->current.corners, g_array_unref);
  g_clear_pointer (&self->current.cutouts, g_array_unref);

  G_OBJECT_CLASS (livi_cutouts_parent_class)->dispose (object);
}


static void
livi_cutouts_class_init (LiviCutoutsClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->get_property = livi_cutouts_get_property;
  object_class->set_property = livi_cutouts_set_property;
  object_class->dispose = livi_cutouts_dispose;

  props[PROP_WINDOW] =
    g_param_spec_object ("window", "", "",
                         GTK_TYPE_WINDOW,
                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  props[PROP_CUTOUTS] =
    g_param_spec_boxed ("cutouts", "", "",
                        G_TYPE_ARRAY,
                        G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  props[PROP_CORNERS] =
    g_param_spec_boxed ("corners", "", "",
                        G_TYPE_ARRAY,
                        G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  signals[UPDATED] = g_signal_new ("updated",
                                   G_TYPE_FROM_CLASS (klass),
                                   G_SIGNAL_RUN_LAST,
                                   0, NULL, NULL, NULL,
                                   G_TYPE_NONE,
                                   0);

  g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
}


static void
livi_cutouts_init (LiviCutouts *self)
{
  self->pending.corners = g_array_new (FALSE, FALSE, sizeof (Corner));
  self->pending.cutouts = g_array_new (FALSE, FALSE, sizeof (Cutout));
  self->current.corners = g_array_new (FALSE, FALSE, sizeof (Corner));
  self->current.cutouts = g_array_new (FALSE, FALSE, sizeof (Cutout));
}


LiviCutouts *
livi_cutouts_new (GtkWindow *self)
{
  return g_object_new (LIVI_TYPE_CUTOUTS,
                       "window", self,
                       NULL);
}

GArray *
livi_cutouts_get_cutouts (LiviCutouts *self)
{
  g_return_val_if_fail (LIVI_IS_CUTOUTS (self), NULL);

  return self->current.cutouts;
}

GArray *
livi_cutouts_get_corners (LiviCutouts *self)
{
  g_return_val_if_fail (LIVI_IS_CUTOUTS (self), NULL);

  return self->current.corners;
}
