#include "config.h"

#include "gdkframeclockprivate.h"
#include "gdkframetimingsprivate.h"
#include "gdkprofilerprivate.h"

#include "gdkwaylandpresentationtime-private.h"

typedef struct _GdkWaylandPresentationFrame
{
  GdkWaylandPresentationTime *self;
  struct wp_presentation_feedback *feedback;
  GdkFrameClock *frame_clock;
  gint64 frame_number;
} GdkWaylandPresentationFrame;

static void
gdk_wayland_presentation_frame_free (GdkWaylandPresentationFrame *frame)
{
  g_clear_pointer (&frame->feedback, wp_presentation_feedback_destroy);
  g_clear_object (&frame->frame_clock);
  frame->self = NULL;
  g_free (frame);
}

static gboolean
gdk_wayland_presentation_frame_same_frame (const void *a_,
                                           const void *b_)
{
  const GdkWaylandPresentationFrame *a = (const GdkWaylandPresentationFrame *) a_;
  const GdkWaylandPresentationFrame *b = (const GdkWaylandPresentationFrame *) b_;

  return a->frame_clock == b->frame_clock &&
         a->frame_number == b->frame_number;
}

struct _GdkWaylandPresentationTime
{
  GdkWaylandDisplay *display;
  GPtrArray *frames;
};

GdkWaylandPresentationTime *
gdk_wayland_presentation_time_new (GdkWaylandDisplay *display)
{
  GdkWaylandPresentationTime *self;

  g_return_val_if_fail (GDK_IS_WAYLAND_DISPLAY (display), NULL);

  self = g_new0 (GdkWaylandPresentationTime, 1);
  self->display = g_object_ref (display);
  self->frames = g_ptr_array_new_with_free_func ((GDestroyNotify)gdk_wayland_presentation_frame_free);

  return self;
}

void
gdk_wayland_presentation_time_free (GdkWaylandPresentationTime *self)
{
  g_clear_pointer (&self->frames, g_ptr_array_unref);
  g_clear_object (&self->display);
  g_free (self);
}

gboolean
gdk_wayland_presentation_time_supported (GdkWaylandPresentationTime *self)
{
  g_return_val_if_fail (self != NULL, FALSE);

  return self->display->presentation != NULL;
}

static uint64_t
time_from_wayland (uint32_t tv_sec_hi,
                   uint32_t tv_sec_lo,
                   uint32_t tv_nsec)
{
  uint64_t t = tv_sec_hi;
  t <<= 32;
  t |= tv_sec_lo;
  t *= G_NSEC_PER_SEC;
  t += tv_nsec;
  return (gint64)t;
}

static void
gdk_wayland_presentation_feedback_sync_output (void                            *data,
                                               struct wp_presentation_feedback *feedback,
                                               struct wl_output                *output)
{
}

static void
gdk_wayland_presentation_feedback_presented (void                            *data,
                                             struct wp_presentation_feedback *feedback,
                                             uint32_t                         tv_sec_hi,
                                             uint32_t                         tv_sec_lo,
                                             uint32_t                         tv_nsec,
                                             uint32_t                         refresh,
                                             uint32_t                         seq_hi,
                                             uint32_t                         seq_lo,
                                             uint32_t                         flags)
{
  GdkWaylandPresentationFrame *frame = data;
  GdkWaylandPresentationTime *self;
  uint32_t pos;
  uint64_t presentation_time;

  g_assert (frame != NULL);
  g_assert (frame->self != NULL);

  self = frame->self;

  presentation_time = time_from_wayland (tv_sec_hi, tv_sec_lo, tv_nsec);
  if (presentation_time != 0)
    {
      gdk_frame_clock_presented (frame->frame_clock,
                                 frame->frame_number,
                                 presentation_time,
                                 refresh);
    }
  else
    {
      gdk_frame_clock_submitted (frame->frame_clock,
                                 frame->frame_number,
                                 refresh);
    }

  if (g_ptr_array_find (frame->self->frames, frame, &pos))
    g_ptr_array_steal_index_fast (frame->self->frames, pos);

  while (g_ptr_array_find_with_equal_func (frame->self->frames,
                                           frame,
                                           gdk_wayland_presentation_frame_same_frame,
                                           &pos))
    {
      g_ptr_array_remove_index_fast (self->frames, pos);
    }

  gdk_wayland_presentation_frame_free (frame);
}

static void
gdk_wayland_presentation_feedback_discarded (void                            *data,
                                             struct wp_presentation_feedback *feedback)
{
  GdkWaylandPresentationFrame *frame = data;
  uint32_t pos;

  g_assert (frame != NULL);
  g_assert (frame->self != NULL);

  if (g_ptr_array_find (frame->self->frames, frame, &pos))
    g_ptr_array_steal_index_fast (frame->self->frames, pos);

  if (!g_ptr_array_find_with_equal_func (frame->self->frames,
                                         frame,
                                         gdk_wayland_presentation_frame_same_frame,
                                         NULL))
    {
      gdk_frame_clock_discarded (frame->frame_clock, frame->frame_number);
    }

  gdk_wayland_presentation_frame_free (frame);
}

static const struct wp_presentation_feedback_listener gdk_wayland_presentation_feedback_listener = {
  gdk_wayland_presentation_feedback_sync_output,
  gdk_wayland_presentation_feedback_presented,
  gdk_wayland_presentation_feedback_discarded,
};

void
gdk_wayland_presentation_time_track (GdkWaylandPresentationTime *self,
                                     GdkFrameClock              *frame_clock,
                                     struct wl_surface          *surface,
                                     gint64                      frame_number)
{
  struct wp_presentation_feedback *feedback;
  GdkWaylandPresentationFrame *frame;

  g_return_if_fail (self != NULL);
  g_return_if_fail (surface != NULL);

  if (self->display->presentation == NULL)
    return;

  if (!(feedback = wp_presentation_feedback (self->display->presentation, surface)))
    return;

  frame = g_new0 (GdkWaylandPresentationFrame, 1);
  frame->self = self;
  frame->frame_clock = g_object_ref (frame_clock);
  frame->frame_number = frame_number;
  frame->feedback = g_steal_pointer (&feedback);

  g_ptr_array_add (self->frames, frame);

  wp_presentation_feedback_add_listener (frame->feedback,
                                         &gdk_wayland_presentation_feedback_listener,
                                         frame);
}
