/*
 * Copyright (C) 2021 Pascal Nowack
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include "config.h"

#include "grd-rdp-event-queue.h"

#include <gio/gio.h>
#include <xkbcommon/xkbcommon-keysyms.h>

typedef enum _RdpEventType
{
  RDP_EVENT_TYPE_NONE,
  RDP_EVENT_TYPE_INPUT_SYNC,
  RDP_EVENT_TYPE_INPUT_KBD_KEYCODE,
  RDP_EVENT_TYPE_INPUT_KBD_KEYSYM,
  RDP_EVENT_TYPE_INPUT_PTR_MOTION,
  RDP_EVENT_TYPE_INPUT_PTR_MOTION_ABS,
  RDP_EVENT_TYPE_INPUT_PTR_BUTTON,
  RDP_EVENT_TYPE_INPUT_PTR_AXIS,
} RdpEventType;

typedef struct _RdpEvent
{
  RdpEventType type;

  /* RDP_EVENT_TYPE_INPUT_KBD_KEYCODE */
  struct
  {
    uint32_t keycode;
    GrdKeyState state;
  } input_kbd_keycode;

  /* RDP_EVENT_TYPE_INPUT_KBD_KEYSYM */
  struct
  {
    uint32_t keysym;
    GrdKeyState state;
  } input_kbd_keysym;

  /* RDP_EVENT_TYPE_INPUT_PTR_MOTION */
  struct
  {
    double dx;
    double dy;
  } input_ptr_motion;

  /* RDP_EVENT_TYPE_INPUT_PTR_MOTION_ABS */
  struct
  {
    GrdStream *stream;
    GrdEventMotionAbs motion_abs;
  } input_ptr_motion_abs;

  /* RDP_EVENT_TYPE_INPUT_PTR_BUTTON */
  struct
  {
    int32_t button;
    GrdButtonState state;
  } input_ptr_button;

  /* RDP_EVENT_TYPE_INPUT_PTR_AXIS */
  struct
  {
    double dx;
    double dy;
    GrdPointerAxisFlags flags;
  } input_ptr_axis;

  /* RDP_EVENT_TYPE_INPUT_SYNC */
  struct
  {
    gboolean caps_lock_state;
    gboolean num_lock_state;
    gboolean needs_sync_ping;
  } input_sync;
} RdpEvent;

struct _GrdRdpEventQueue
{
  GObject parent;

  GrdSessionRdp *session_rdp;
  GSource *flush_source;

  GMutex event_mutex;
  GQueue *queue;

  GCancellable *pending_sync_cancellable;
  gboolean expected_caps_lock_state;
  gboolean expected_num_lock_state;
  gboolean caps_lock_state;
  gboolean num_lock_state;
};

G_DEFINE_TYPE (GrdRdpEventQueue, grd_rdp_event_queue, G_TYPE_OBJECT)

void
free_rdp_event (gpointer data)
{
  RdpEvent *rdp_event = data;

  g_clear_object (&rdp_event->input_ptr_motion_abs.stream);
  g_free (rdp_event);
}

static void
flush_input_cb (GObject      *source_object,
                GAsyncResult *result,
                gpointer      user_data)
{
  GrdSession *session = GRD_SESSION (source_object);
  g_autoptr (GError) error = NULL;
  GrdRdpEventQueue *rdp_event_queue;

  if (!grd_session_flush_input_finish (session, result, &error))
    {
      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        g_warning ("Failed to flush input: %s", error->message);

      return;
    }

  rdp_event_queue = GRD_RDP_EVENT_QUEUE (user_data);
  g_clear_object (&rdp_event_queue->pending_sync_cancellable);

  if (rdp_event_queue->expected_caps_lock_state != rdp_event_queue->caps_lock_state)
    {
      g_debug ("Synchronizing caps lock state to be %s, pressing caps lock key",
               rdp_event_queue->expected_caps_lock_state ? "locked": "unlocked");

      grd_session_notify_keyboard_keysym (session, XKB_KEY_Caps_Lock,
                                          GRD_KEY_STATE_PRESSED);
      grd_session_notify_keyboard_keysym (session, XKB_KEY_Caps_Lock,
                                          GRD_KEY_STATE_RELEASED);

    }
  if (rdp_event_queue->expected_num_lock_state != rdp_event_queue->num_lock_state)
    {
      g_debug ("Synchronizing num lock state to be %s, pressing num lock key",
               rdp_event_queue->expected_num_lock_state ? "locked": "unlocked");

      grd_session_notify_keyboard_keysym (session, XKB_KEY_Num_Lock,
                                          GRD_KEY_STATE_PRESSED);
      grd_session_notify_keyboard_keysym (session, XKB_KEY_Num_Lock,
                                          GRD_KEY_STATE_RELEASED);
    }
}

static void
handle_synchronization_event (GrdRdpEventQueue *rdp_event_queue,
                              RdpEvent         *rdp_event)
{
  GrdSession *session = GRD_SESSION (rdp_event_queue->session_rdp);

  g_cancellable_cancel (rdp_event_queue->pending_sync_cancellable);
  g_clear_object (&rdp_event_queue->pending_sync_cancellable);

  rdp_event_queue->expected_caps_lock_state = rdp_event->input_sync.caps_lock_state;
  rdp_event_queue->expected_num_lock_state = rdp_event->input_sync.num_lock_state;
  rdp_event_queue->pending_sync_cancellable = g_cancellable_new ();

  if (!grd_session_is_ready (session))
    return;

  grd_rdp_event_queue_flush_synchronization (rdp_event_queue);
}

void
grd_rdp_event_queue_flush_synchronization (GrdRdpEventQueue *rdp_event_queue)
{
  g_assert (grd_session_is_ready (GRD_SESSION (rdp_event_queue->session_rdp)));

  if (!rdp_event_queue->pending_sync_cancellable)
    return;

  grd_session_flush_input_async (GRD_SESSION (rdp_event_queue->session_rdp),
                                 rdp_event_queue->pending_sync_cancellable,
                                 flush_input_cb,
                                 rdp_event_queue);
}

static void
process_rdp_events (GrdRdpEventQueue *rdp_event_queue)
{
  GrdSession *session = GRD_SESSION (rdp_event_queue->session_rdp);
  RdpEvent *rdp_event;

  while ((rdp_event = g_queue_pop_head (rdp_event_queue->queue)))
    {
      switch (rdp_event->type)
        {
        case RDP_EVENT_TYPE_NONE:
          break;
        case RDP_EVENT_TYPE_INPUT_SYNC:
          handle_synchronization_event (rdp_event_queue, rdp_event);
          break;
        case RDP_EVENT_TYPE_INPUT_KBD_KEYCODE:
          grd_session_notify_keyboard_keycode (
            session, rdp_event->input_kbd_keycode.keycode,
            rdp_event->input_kbd_keycode.state);
          break;
        case RDP_EVENT_TYPE_INPUT_KBD_KEYSYM:
          grd_session_notify_keyboard_keysym (session,
                                              rdp_event->input_kbd_keysym.keysym,
                                              rdp_event->input_kbd_keysym.state);
          break;
        case RDP_EVENT_TYPE_INPUT_PTR_MOTION:
          grd_session_notify_pointer_motion (session,
                                             rdp_event->input_ptr_motion.dx,
                                             rdp_event->input_ptr_motion.dy);
          break;
        case RDP_EVENT_TYPE_INPUT_PTR_MOTION_ABS:
          grd_session_notify_pointer_motion_absolute (
            session, rdp_event->input_ptr_motion_abs.stream,
            &rdp_event->input_ptr_motion_abs.motion_abs);
          break;
        case RDP_EVENT_TYPE_INPUT_PTR_BUTTON:
          grd_session_notify_pointer_button (session,
                                             rdp_event->input_ptr_button.button,
                                             rdp_event->input_ptr_button.state);
          break;
        case RDP_EVENT_TYPE_INPUT_PTR_AXIS:
          grd_session_notify_pointer_axis (session,
                                           rdp_event->input_ptr_axis.dx,
                                           rdp_event->input_ptr_axis.dy,
                                           rdp_event->input_ptr_axis.flags);
          break;
        }

      free_rdp_event (rdp_event);
    }
}

void
grd_rdp_event_queue_flush (GrdRdpEventQueue *rdp_event_queue)
{
  g_mutex_lock (&rdp_event_queue->event_mutex);
  process_rdp_events (rdp_event_queue);
  g_mutex_unlock (&rdp_event_queue->event_mutex);
}

static void
queue_rdp_event (GrdRdpEventQueue *rdp_event_queue,
                 RdpEvent         *rdp_event)
{
  g_mutex_lock (&rdp_event_queue->event_mutex);
  g_queue_push_tail (rdp_event_queue->queue, rdp_event);
  g_mutex_unlock (&rdp_event_queue->event_mutex);

  g_source_set_ready_time (rdp_event_queue->flush_source, 0);
}

void
grd_rdp_event_queue_add_input_event_keyboard_keycode (GrdRdpEventQueue *rdp_event_queue,
                                                      uint32_t          keycode,
                                                      GrdKeyState       state)
{
  RdpEvent *rdp_event;

  rdp_event = g_malloc0 (sizeof (RdpEvent));
  rdp_event->type = RDP_EVENT_TYPE_INPUT_KBD_KEYCODE;
  rdp_event->input_kbd_keycode.keycode = keycode;
  rdp_event->input_kbd_keycode.state = state;

  queue_rdp_event (rdp_event_queue, rdp_event);
}

void
grd_rdp_event_queue_add_input_event_keyboard_keysym (GrdRdpEventQueue *rdp_event_queue,
                                                     uint32_t          keysym,
                                                     GrdKeyState       state)
{
  RdpEvent *rdp_event;

  rdp_event = g_malloc0 (sizeof (RdpEvent));
  rdp_event->type = RDP_EVENT_TYPE_INPUT_KBD_KEYSYM;
  rdp_event->input_kbd_keysym.keysym = keysym;
  rdp_event->input_kbd_keysym.state = state;

  queue_rdp_event (rdp_event_queue, rdp_event);
}

void
grd_rdp_event_queue_add_input_event_pointer_motion (GrdRdpEventQueue *rdp_event_queue,
                                                    double            dx,
                                                    double            dy)
{
  RdpEvent *rdp_event;

  rdp_event = g_new0 (RdpEvent, 1);
  rdp_event->type = RDP_EVENT_TYPE_INPUT_PTR_MOTION;
  rdp_event->input_ptr_motion.dx = dx;
  rdp_event->input_ptr_motion.dy = dy;

  queue_rdp_event (rdp_event_queue, rdp_event);
}

void
grd_rdp_event_queue_add_input_event_pointer_motion_abs (GrdRdpEventQueue        *rdp_event_queue,
                                                        GrdStream               *stream,
                                                        const GrdEventMotionAbs *motion_abs)
{
  RdpEvent *rdp_event;

  rdp_event = g_malloc0 (sizeof (RdpEvent));
  rdp_event->type = RDP_EVENT_TYPE_INPUT_PTR_MOTION_ABS;
  rdp_event->input_ptr_motion_abs.stream = stream;
  rdp_event->input_ptr_motion_abs.motion_abs = *motion_abs;

  queue_rdp_event (rdp_event_queue, rdp_event);
}

void
grd_rdp_event_queue_add_input_event_pointer_button (GrdRdpEventQueue *rdp_event_queue,
                                                    int32_t           button,
                                                    GrdButtonState    state)
{
  RdpEvent *rdp_event;

  rdp_event = g_malloc0 (sizeof (RdpEvent));
  rdp_event->type = RDP_EVENT_TYPE_INPUT_PTR_BUTTON;
  rdp_event->input_ptr_button.button = button;
  rdp_event->input_ptr_button.state = state;

  queue_rdp_event (rdp_event_queue, rdp_event);
}

void
grd_rdp_event_queue_add_input_event_pointer_axis (GrdRdpEventQueue    *rdp_event_queue,
                                                  double               dx,
                                                  double               dy,
                                                  GrdPointerAxisFlags  flags)
{
  RdpEvent *rdp_event;

  rdp_event = g_malloc0 (sizeof (RdpEvent));
  rdp_event->type = RDP_EVENT_TYPE_INPUT_PTR_AXIS;
  rdp_event->input_ptr_axis.dx = dx;
  rdp_event->input_ptr_axis.dy = dy;
  rdp_event->input_ptr_axis.flags = flags;

  queue_rdp_event (rdp_event_queue, rdp_event);
}

void
grd_rdp_event_queue_update_caps_lock_state (GrdRdpEventQueue *rdp_event_queue,
                                            gboolean          caps_lock_state)
{
  g_debug ("Updated current caps lock state to %s",
           caps_lock_state ? "locked" : "unlocked");
  rdp_event_queue->caps_lock_state = caps_lock_state;
}

void
grd_rdp_event_queue_update_num_lock_state (GrdRdpEventQueue *rdp_event_queue,
                                           gboolean          num_lock_state)
{
  g_debug ("Updated current num lock state to %s",
           num_lock_state ? "locked" : "unlocked");
  rdp_event_queue->num_lock_state = num_lock_state;
}

void
grd_rdp_event_queue_add_synchronization_event (GrdRdpEventQueue *rdp_event_queue,
                                               gboolean          caps_lock_state,
                                               gboolean          num_lock_state)
{
  RdpEvent *rdp_event;

  rdp_event = g_new0 (RdpEvent, 1);
  rdp_event->type = RDP_EVENT_TYPE_INPUT_SYNC;
  rdp_event->input_sync.caps_lock_state = caps_lock_state;
  rdp_event->input_sync.num_lock_state = num_lock_state;
  rdp_event->input_sync.needs_sync_ping = TRUE;

  queue_rdp_event (rdp_event_queue, rdp_event);
}

static gboolean
flush_rdp_events (gpointer user_data)
{
  GrdRdpEventQueue *rdp_event_queue = user_data;

  g_mutex_lock (&rdp_event_queue->event_mutex);
  process_rdp_events (rdp_event_queue);
  g_mutex_unlock (&rdp_event_queue->event_mutex);

  return G_SOURCE_CONTINUE;
}

static gboolean
flush_source_dispatch (GSource     *source,
                       GSourceFunc  callback,
                       gpointer     user_data)
{
  g_source_set_ready_time (source, -1);

  return callback (user_data);
}

static GSourceFuncs flush_source_funcs =
{
  .dispatch = flush_source_dispatch,
};

GrdRdpEventQueue *
grd_rdp_event_queue_new (GrdSessionRdp *session_rdp)
{
  GrdRdpEventQueue *rdp_event_queue;
  GSource *flush_source;

  rdp_event_queue = g_object_new (GRD_TYPE_RDP_EVENT_QUEUE, NULL);
  rdp_event_queue->session_rdp = session_rdp;

  flush_source = g_source_new (&flush_source_funcs, sizeof (GSource));
  g_source_set_callback (flush_source, flush_rdp_events, rdp_event_queue, NULL);
  g_source_set_ready_time (flush_source, -1);
  g_source_attach (flush_source, NULL);
  rdp_event_queue->flush_source = flush_source;

  return rdp_event_queue;
}

static void
grd_rdp_event_queue_dispose (GObject *object)
{
  GrdRdpEventQueue *rdp_event_queue = GRD_RDP_EVENT_QUEUE (object);

  if (rdp_event_queue->flush_source)
    {
      g_source_destroy (rdp_event_queue->flush_source);
      g_clear_pointer (&rdp_event_queue->flush_source, g_source_unref);
    }

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

static void
grd_rdp_event_queue_finalize (GObject *object)
{
  GrdRdpEventQueue *rdp_event_queue = GRD_RDP_EVENT_QUEUE (object);

  g_cancellable_cancel (rdp_event_queue->pending_sync_cancellable);
  g_clear_object (&rdp_event_queue->pending_sync_cancellable);

  g_mutex_clear (&rdp_event_queue->event_mutex);

  g_queue_free_full (rdp_event_queue->queue, free_rdp_event);

  G_OBJECT_CLASS (grd_rdp_event_queue_parent_class)->finalize (object);
}

static void
grd_rdp_event_queue_init (GrdRdpEventQueue *rdp_event_queue)
{
  rdp_event_queue->queue = g_queue_new ();

  g_mutex_init (&rdp_event_queue->event_mutex);
}

static void
grd_rdp_event_queue_class_init (GrdRdpEventQueueClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = grd_rdp_event_queue_dispose;
  object_class->finalize = grd_rdp_event_queue_finalize;
}
