/*
 * Copyright (C) 2025 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-dvc.h"

typedef struct
{
  GrdRdpDvcHandler *dvc_handler;
  GrdSessionRdp *session_rdp;
  GrdRdpChannel channel;

  gboolean channel_unavailable;

  uint32_t channel_id;
  uint32_t subscription_id;
  gboolean is_subscribed;

  GSource *channel_teardown_source;
} GrdRdpDvcPrivate;

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GrdRdpDvc, grd_rdp_dvc,
                                     G_TYPE_OBJECT)

void
grd_rdp_dvc_initialize_base (GrdRdpDvc        *dvc,
                             GrdRdpDvcHandler *dvc_handler,
                             GrdSessionRdp    *session_rdp,
                             GrdRdpChannel     channel)
{
  GrdRdpDvcPrivate *priv = grd_rdp_dvc_get_instance_private (dvc);

  priv->dvc_handler = dvc_handler;
  priv->session_rdp = session_rdp;
  priv->channel = channel;
}

void
grd_rdp_dvc_maybe_init (GrdRdpDvc *dvc)
{
  GrdRdpDvcPrivate *priv = grd_rdp_dvc_get_instance_private (dvc);
  GrdRdpDvcClass *klass = GRD_RDP_DVC_GET_CLASS (dvc);

  if (priv->channel_unavailable)
    return;

  klass->maybe_init (dvc);
}

void
grd_rdp_dvc_queue_channel_tear_down (GrdRdpDvc *dvc)
{
  GrdRdpDvcPrivate *priv = grd_rdp_dvc_get_instance_private (dvc);

  priv->channel_unavailable = TRUE;

  g_source_set_ready_time (priv->channel_teardown_source, 0);
}

void
grd_rdp_dvc_subscribe_creation_status (GrdRdpDvc                       *dvc,
                                       uint32_t                         channel_id,
                                       GrdRdpDVCCreationStatusCallback  callback,
                                       gpointer                         callback_user_data)
{
  GrdRdpDvcPrivate *priv = grd_rdp_dvc_get_instance_private (dvc);

  g_assert (!priv->is_subscribed);

  priv->channel_id = channel_id;

  priv->subscription_id =
    grd_rdp_dvc_handler_subscribe_dvc_creation_status (priv->dvc_handler,
                                                       channel_id,
                                                       callback,
                                                       callback_user_data);
  priv->is_subscribed = TRUE;
}

void
grd_rdp_dvc_maybe_unsubscribe_creation_status (GrdRdpDvc *dvc)
{
  GrdRdpDvcPrivate *priv = grd_rdp_dvc_get_instance_private (dvc);

  if (!priv->is_subscribed)
    return;

  grd_rdp_dvc_handler_unsubscribe_dvc_creation_status (priv->dvc_handler,
                                                       priv->channel_id,
                                                       priv->subscription_id);
  priv->is_subscribed = FALSE;
}

static void
grd_rdp_dvc_dispose (GObject *object)
{
  GrdRdpDvc *dvc = GRD_RDP_DVC (object);
  GrdRdpDvcPrivate *priv = grd_rdp_dvc_get_instance_private (dvc);

  if (priv->channel_teardown_source)
    {
      g_source_destroy (priv->channel_teardown_source);
      g_clear_pointer (&priv->channel_teardown_source, g_source_unref);
    }

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

static const char *
channel_to_string (GrdRdpChannel channel)
{
  switch (channel)
    {
    case GRD_RDP_CHANNEL_NONE:
      g_assert_not_reached ();
      break;
    case GRD_RDP_CHANNEL_AUDIO_INPUT:
      return "AUDIO_INPUT";
    case GRD_RDP_CHANNEL_AUDIO_PLAYBACK:
      return "AUDIO_PLAYBACK";
    case GRD_RDP_CHANNEL_CAMERA:
      return "CAM";
    case GRD_RDP_CHANNEL_CAMERA_ENUMERATOR:
      return "CAM_ENUMERATOR";
    case GRD_RDP_CHANNEL_DISPLAY_CONTROL:
      return "DISP";
    case GRD_RDP_CHANNEL_GRAPHICS_PIPELINE:
      return "RDPGFX";
    case GRD_RDP_CHANNEL_INPUT:
      return "INPUT";
    case GRD_RDP_CHANNEL_TELEMETRY:
      return "TELEMETRY";
    }

  g_assert_not_reached ();
}

static gboolean
tear_down_channel (gpointer user_data)
{
  GrdRdpDvc *dvc = user_data;
  GrdRdpDvcPrivate *priv = grd_rdp_dvc_get_instance_private (dvc);

  g_debug ("[RDP.%s] Tearing down channel", channel_to_string (priv->channel));

  g_clear_pointer (&priv->channel_teardown_source, g_source_unref);
  grd_session_rdp_tear_down_channel (priv->session_rdp, priv->channel);

  return G_SOURCE_REMOVE;
}

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

  return callback (user_data);
}

static GSourceFuncs source_funcs =
{
  .dispatch = source_dispatch,
};

static void
grd_rdp_dvc_init (GrdRdpDvc *dvc)
{
  GrdRdpDvcPrivate *priv = grd_rdp_dvc_get_instance_private (dvc);
  GSource *channel_teardown_source;

  channel_teardown_source = g_source_new (&source_funcs, sizeof (GSource));
  g_source_set_callback (channel_teardown_source, tear_down_channel,
                         dvc, NULL);
  g_source_set_ready_time (channel_teardown_source, -1);
  g_source_attach (channel_teardown_source, NULL);
  priv->channel_teardown_source = channel_teardown_source;
}

static void
grd_rdp_dvc_class_init (GrdRdpDvcClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = grd_rdp_dvc_dispose;
}
