/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */

/*
 * Copyright (C) 2017 Red Hat Inc.
 *
 * 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 "backends/meta-screen-cast-stream.h"

#include "backends/meta-screen-cast-session.h"

#include "meta-private-enum-types.h"

#define META_SCREEN_CAST_STREAM_DBUS_IFACE "org.gnome.Mutter.ScreenCast.Stream"
#define META_SCREEN_CAST_STREAM_DBUS_PATH "/org/gnome/Mutter/ScreenCast/Stream"

enum
{
  PROP_0,

  PROP_SESSION,
  PROP_CONNECTION,
  PROP_CURSOR_MODE,
  PROP_FLAGS,
};

enum
{
  CLOSED,

  N_SIGNALS
};

static guint signals[N_SIGNALS];

typedef struct _MetaScreenCastStreamPrivate
{
  MetaScreenCastSession *session;

  GDBusConnection *connection;
  char *object_path;

  MetaScreenCastCursorMode cursor_mode;
  MetaScreenCastFlag flags;

  MetaScreenCastStreamSrc *src;
} MetaScreenCastStreamPrivate;

static void
meta_screen_cast_stream_init_iface (MetaDBusScreenCastStreamIface *iface);

static void
meta_screen_cast_stream_init_initable_iface (GInitableIface *iface);

G_DEFINE_TYPE_WITH_CODE (MetaScreenCastStream,
                         meta_screen_cast_stream,
                         META_DBUS_TYPE_SCREEN_CAST_STREAM_SKELETON,
                         G_IMPLEMENT_INTERFACE (META_DBUS_TYPE_SCREEN_CAST_STREAM,
                                                meta_screen_cast_stream_init_iface)
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                                meta_screen_cast_stream_init_initable_iface)
                         G_ADD_PRIVATE (MetaScreenCastStream))

static MetaScreenCastStreamSrc *
meta_screen_cast_stream_create_src (MetaScreenCastStream  *stream,
                                    GError               **error)
{
  return META_SCREEN_CAST_STREAM_GET_CLASS (stream)->create_src (stream,
                                                                 error);
}

static void
meta_screen_cast_stream_set_parameters (MetaScreenCastStream *stream,
                                        GVariantBuilder      *parameters_builder)
{
  META_SCREEN_CAST_STREAM_GET_CLASS (stream)->set_parameters (stream,
                                                              parameters_builder);
}

static void
on_stream_src_closed (MetaScreenCastStreamSrc *src,
                      MetaScreenCastStream    *stream)
{
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);

  if (priv->src)
    meta_screen_cast_stream_close (stream);
}

static void
on_stream_src_ready (MetaScreenCastStreamSrc *src,
                     uint32_t                 node_id,
                     MetaScreenCastStream    *stream)
{
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);
  GDBusConnection *connection = priv->connection;
  char *peer_name;

  peer_name = meta_screen_cast_session_get_peer_name (priv->session);
  g_dbus_connection_emit_signal (connection,
                                 peer_name,
                                 priv->object_path,
                                 META_SCREEN_CAST_STREAM_DBUS_IFACE,
                                 "PipeWireStreamAdded",
                                 g_variant_new ("(u)", node_id),
                                 NULL);
}

MetaScreenCastSession *
meta_screen_cast_stream_get_session (MetaScreenCastStream *stream)
{
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);

  return priv->session;
}

gboolean
meta_screen_cast_stream_start (MetaScreenCastStream  *stream,
                               GError               **error)
{
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);
  MetaScreenCastStreamSrc *src;

  if (priv->src)
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                   "Stream already started");
      return FALSE;
    }

  src = meta_screen_cast_stream_create_src (stream, error);
  if (!src)
    return FALSE;

  priv->src = src;
  g_signal_connect (src, "ready", G_CALLBACK (on_stream_src_ready), stream);
  g_signal_connect (src, "closed", G_CALLBACK (on_stream_src_closed), stream);

  return TRUE;
}

void
meta_screen_cast_stream_close (MetaScreenCastStream *stream)
{
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);

  g_clear_object (&priv->src);

  g_signal_emit (stream, signals[CLOSED], 0);
}

char *
meta_screen_cast_stream_get_object_path (MetaScreenCastStream *stream)
{
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);

  return priv->object_path;
}

MetaScreenCastStreamSrc *
meta_screen_cast_stream_get_src (MetaScreenCastStream *stream)
{
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);

  return priv->src;
}

gboolean
meta_screen_cast_stream_transform_position (MetaScreenCastStream *stream,
                                            double                stream_x,
                                            double                stream_y,
                                            double               *x,
                                            double               *y)
{
  MetaScreenCastStreamClass *klass = META_SCREEN_CAST_STREAM_GET_CLASS (stream);

  return klass->transform_position (stream, stream_x, stream_y, x, y);
}

MetaScreenCastCursorMode
meta_screen_cast_stream_get_cursor_mode (MetaScreenCastStream *stream)
{
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);

  return priv->cursor_mode;
}

MetaScreenCastFlag
meta_screen_cast_stream_get_flags (MetaScreenCastStream *stream)
{
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);

  return priv->flags;
}

static void
meta_screen_cast_stream_set_property (GObject      *object,
                                      guint         prop_id,
                                      const GValue *value,
                                      GParamSpec   *pspec)
{
  MetaScreenCastStream *stream = META_SCREEN_CAST_STREAM (object);
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);

  switch (prop_id)
    {
    case PROP_SESSION:
      priv->session = g_value_get_object (value);
      break;
    case PROP_CONNECTION:
      priv->connection = g_value_get_object (value);
      break;
    case PROP_CURSOR_MODE:
      priv->cursor_mode = g_value_get_uint (value);
      break;
    case PROP_FLAGS:
      priv->flags = g_value_get_flags (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
meta_screen_cast_stream_get_property (GObject    *object,
                                      guint       prop_id,
                                      GValue     *value,
                                      GParamSpec *pspec)
{
  MetaScreenCastStream *stream = META_SCREEN_CAST_STREAM (object);
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);

  switch (prop_id)
    {
    case PROP_SESSION:
      g_value_set_object (value, priv->session);
      break;
    case PROP_CONNECTION:
      g_value_set_object (value, priv->connection);
      break;
    case PROP_CURSOR_MODE:
      g_value_set_uint (value, priv->cursor_mode);
      break;
    case PROP_FLAGS:
      g_value_set_flags (value, priv->flags);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
meta_screen_cast_stream_finalize (GObject *object)
{
  MetaScreenCastStream *stream = META_SCREEN_CAST_STREAM (object);
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);

  if (priv->src)
    meta_screen_cast_stream_close (stream);

  g_clear_pointer (&priv->object_path, g_free);

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

static gboolean
check_permission (MetaScreenCastStream  *stream,
                  GDBusMethodInvocation *invocation)
{
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);
  char *peer_name;

  peer_name = meta_screen_cast_session_get_peer_name (priv->session);
  return g_strcmp0 (peer_name,
                    g_dbus_method_invocation_get_sender (invocation)) == 0;
}

static gboolean
handle_start (MetaDBusScreenCastStream *skeleton,
              GDBusMethodInvocation    *invocation)
{
  MetaScreenCastStream *stream = META_SCREEN_CAST_STREAM (skeleton);
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);
  g_autoptr (GError) error = NULL;

  if (!check_permission (stream, invocation))
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                             G_DBUS_ERROR_ACCESS_DENIED,
                                             "Permission denied");
      return TRUE;
    }

  if (!meta_screen_cast_session_is_active (priv->session))
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                             G_DBUS_ERROR_FAILED,
                                             "Failed to start stream: "
                                             "session not started");
      return TRUE;
    }

  if (!meta_screen_cast_stream_start (stream, &error))
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                             G_DBUS_ERROR_FAILED,
                                             "Failed to start stream: %s",
                                             error->message);
      return TRUE;
    }

  meta_dbus_screen_cast_stream_complete_start (skeleton, invocation);

  return TRUE;
}

static void
meta_screen_cast_stream_init_iface (MetaDBusScreenCastStreamIface *iface)
{
  iface->handle_start = handle_start;
}

static gboolean
meta_screen_cast_stream_initable_init (GInitable     *initable,
                                       GCancellable  *cancellable,
                                       GError       **error)
{
  MetaScreenCastStream *stream = META_SCREEN_CAST_STREAM (initable);
  MetaDBusScreenCastStream *skeleton = META_DBUS_SCREEN_CAST_STREAM (stream);
  MetaScreenCastStreamPrivate *priv =
    meta_screen_cast_stream_get_instance_private (stream);
  GVariantBuilder parameters_builder;
  GVariant *parameters_variant;
  static unsigned int global_stream_number = 0;

  g_variant_builder_init (&parameters_builder, G_VARIANT_TYPE_VARDICT);
  meta_screen_cast_stream_set_parameters (stream, &parameters_builder);

  parameters_variant = g_variant_builder_end (&parameters_builder);
  meta_dbus_screen_cast_stream_set_parameters (skeleton, parameters_variant);

  priv->object_path =
    g_strdup_printf (META_SCREEN_CAST_STREAM_DBUS_PATH "/u%u",
                     ++global_stream_number);
  if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (stream),
                                         priv->connection,
                                         priv->object_path,
                                         error))
    return FALSE;

  return TRUE;
}

static void
meta_screen_cast_stream_init_initable_iface (GInitableIface *iface)
{
  iface->init = meta_screen_cast_stream_initable_init;
}

static void
meta_screen_cast_stream_init (MetaScreenCastStream *stream)
{
}

static void
meta_screen_cast_stream_class_init (MetaScreenCastStreamClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = meta_screen_cast_stream_finalize;
  object_class->set_property = meta_screen_cast_stream_set_property;
  object_class->get_property = meta_screen_cast_stream_get_property;

  g_object_class_install_property (object_class,
                                   PROP_SESSION,
                                   g_param_spec_object ("session",
                                                        "session",
                                                        "MetaScreenSession",
                                                        META_TYPE_SCREEN_CAST_SESSION,
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY |
                                                        G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class,
                                   PROP_CONNECTION,
                                   g_param_spec_object ("connection",
                                                        "connection",
                                                        "GDBus connection",
                                                        G_TYPE_DBUS_CONNECTION,
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY |
                                                        G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class,
                                   PROP_CURSOR_MODE,
                                   g_param_spec_uint ("cursor-mode",
                                                      "cursor-mode",
                                                      "Cursor mode",
                                                      META_SCREEN_CAST_CURSOR_MODE_HIDDEN,
                                                      META_SCREEN_CAST_CURSOR_MODE_METADATA,
                                                      META_SCREEN_CAST_CURSOR_MODE_HIDDEN,
                                                      G_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT_ONLY |
                                                      G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class,
                                   PROP_FLAGS,
                                   g_param_spec_flags ("flags",
                                                       "flags",
                                                       "Screen cast flags",
                                                       META_TYPE_SCREEN_CAST_FLAG,
                                                       META_SCREEN_CAST_FLAG_NONE,
                                                       G_PARAM_READWRITE |
                                                       G_PARAM_CONSTRUCT_ONLY |
                                                       G_PARAM_STATIC_STRINGS));

  signals[CLOSED] = g_signal_new ("closed",
                                  G_TYPE_FROM_CLASS (klass),
                                  G_SIGNAL_RUN_LAST,
                                  0,
                                  NULL, NULL, NULL,
                                  G_TYPE_NONE, 0);
}
