/*
 * Copyright © 2011 Kristian Høgsberg
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of the copyright holders not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no representations
 * about the suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */

/* The file is based on src/data-device.c from Weston */

#include "config.h"

#include "wayland/meta-wayland-data-device-primary-legacy.h"

#include "compositor/meta-dnd-actor-private.h"
#include "meta/meta-selection-source-memory.h"
#include "wayland/meta-selection-source-wayland-private.h"
#include "wayland/meta-wayland-data-offer-primary-legacy.h"
#include "wayland/meta-wayland-data-source-primary-legacy.h"
#include "wayland/meta-wayland-dnd-surface.h"
#include "wayland/meta-wayland-pointer.h"
#include "wayland/meta-wayland-private.h"
#include "wayland/meta-wayland-seat.h"

#include "gtk-primary-selection-server-protocol.h"

static struct wl_resource * create_and_send_primary_offer (MetaWaylandDataDevicePrimaryLegacy *data_device,
                                                           struct wl_resource                 *target);

static void
move_resources (struct wl_list *destination,
                struct wl_list *source)
{
  wl_list_insert_list (destination, source);
  wl_list_init (source);
}

static void
move_resources_for_client (struct wl_list   *destination,
			   struct wl_list   *source,
			   struct wl_client *client)
{
  struct wl_resource *resource, *tmp;
  wl_resource_for_each_safe (resource, tmp, source)
    {
      if (wl_resource_get_client (resource) == client)
        {
          wl_list_remove (wl_resource_get_link (resource));
          wl_list_insert (destination, wl_resource_get_link (resource));
        }
    }
}

static void
unbind_resource (struct wl_resource *resource)
{
  wl_list_remove (wl_resource_get_link (resource));
}

static void
default_destructor (struct wl_client   *client,
                    struct wl_resource *resource)
{
  wl_resource_destroy (resource);
}

static void
set_selection_source (MetaWaylandDataDevicePrimaryLegacy *data_device,
                      MetaSelectionSource                *selection_source)

{
  MetaDisplay *display = meta_get_display ();

  meta_selection_set_owner (meta_display_get_selection (display),
                            META_SELECTION_PRIMARY,
                            selection_source);
  g_set_object (&data_device->owner, selection_source);
}

static void
unset_selection_source (MetaWaylandDataDevicePrimaryLegacy *data_device)
{
  MetaDisplay *display = meta_get_display ();

  if (!data_device->owner)
    return;

  meta_selection_unset_owner (meta_display_get_selection (display),
                              META_SELECTION_PRIMARY,
                              data_device->owner);
  g_clear_object (&data_device->owner);
}

static void
primary_source_destroyed (gpointer  data,
                          GObject  *object_was_here)
{
  MetaWaylandDataDevicePrimaryLegacy *data_device = data;

  data_device->data_source = NULL;
  unset_selection_source (data_device);
}

static void
meta_wayland_data_device_primary_legacy_set_selection (MetaWaylandDataDevicePrimaryLegacy *data_device,
                                                       MetaWaylandDataSource              *source,
                                                       uint32_t                            serial)
{
  MetaWaylandSeat *seat = wl_container_of (data_device, seat, primary_legacy_data_device);
  MetaSelectionSource *selection_source;

  g_assert (!source || META_IS_WAYLAND_DATA_SOURCE_PRIMARY_LEGACY (source));

  if (data_device->data_source &&
      data_device->serial - serial < UINT32_MAX / 2)
    return;

  if (data_device->data_source)
    {
      g_object_weak_unref (G_OBJECT (data_device->data_source),
                           primary_source_destroyed,
                           data_device);
    }

  data_device->data_source = source;
  data_device->serial = serial;

  if (source)
    {
      meta_wayland_data_source_set_seat (source, seat);
      g_object_weak_ref (G_OBJECT (source),
                         primary_source_destroyed,
                         data_device);

      selection_source = meta_selection_source_wayland_new (source);
    }
  else
    {
      selection_source = g_object_new (META_TYPE_SELECTION_SOURCE_MEMORY, NULL);
    }

  set_selection_source (data_device, selection_source);
  g_object_unref (selection_source);
}

static void
primary_device_set_selection (struct wl_client   *client,
                              struct wl_resource *resource,
                              struct wl_resource *source_resource,
                              uint32_t            serial)
{
  MetaWaylandDataDevicePrimaryLegacy *data_device = wl_resource_get_user_data (resource);
  MetaWaylandSeat *seat = wl_container_of (data_device, seat, primary_legacy_data_device);
  MetaWaylandDataSource *source = NULL;

  if (source_resource)
    source = wl_resource_get_user_data (source_resource);

  if (wl_resource_get_client (resource) !=
      meta_wayland_keyboard_get_focus_client (seat->keyboard))
    return;

  meta_wayland_data_device_primary_legacy_set_selection (data_device, source, serial);
}

static const struct gtk_primary_selection_device_interface primary_device_interface = {
  primary_device_set_selection,
  default_destructor,
};

static void
owner_changed_cb (MetaSelection                      *selection,
                  MetaSelectionType                   selection_type,
                  MetaSelectionSource                *new_owner,
                  MetaWaylandDataDevicePrimaryLegacy *data_device)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  MetaWaylandSeat *seat = compositor->seat;
  struct wl_resource *data_device_resource;
  struct wl_client *focus_client;

  focus_client = meta_wayland_keyboard_get_focus_client (seat->keyboard);
  if (!focus_client)
    return;

  if (selection_type == META_SELECTION_PRIMARY)
    {
      wl_resource_for_each (data_device_resource, &data_device->focus_resource_list)
        {
          struct wl_resource *offer = NULL;

          if (new_owner)
            {
              offer = create_and_send_primary_offer (data_device,
                                                     data_device_resource);
            }

          gtk_primary_selection_device_send_selection (data_device_resource,
                                                       offer);
        }
    }
}

static void
ensure_owners_changed_handler_connected (MetaWaylandDataDevicePrimaryLegacy *data_device)
{
  if (data_device->selection_owner_signal_id != 0)
    return;

  data_device->selection_owner_signal_id =
    g_signal_connect (meta_display_get_selection (meta_get_display ()),
                      "owner-changed",
                      G_CALLBACK (owner_changed_cb), data_device);
}

static void
primary_device_manager_create_source (struct wl_client   *client,
                                      struct wl_resource *manager_resource,
                                      guint32             id)
{
  struct wl_resource *source_resource;

  source_resource =
    wl_resource_create (client, &gtk_primary_selection_source_interface,
                        wl_resource_get_version (manager_resource),
                        id);
  meta_wayland_data_source_primary_legacy_new (source_resource);
}

static void
primary_device_manager_get_device (struct wl_client   *client,
                                   struct wl_resource *manager_resource,
                                   guint32             id,
                                   struct wl_resource *seat_resource)
{
  MetaWaylandSeat *seat = wl_resource_get_user_data (seat_resource);
  struct wl_resource *cr;

  cr = wl_resource_create (client, &gtk_primary_selection_device_interface,
                           wl_resource_get_version (manager_resource), id);
  wl_resource_set_implementation (cr, &primary_device_interface,
                                  &seat->primary_legacy_data_device, unbind_resource);
  wl_list_insert (&seat->primary_legacy_data_device.resource_list,
                  wl_resource_get_link (cr));

  ensure_owners_changed_handler_connected (&seat->primary_legacy_data_device);
}

static const struct gtk_primary_selection_device_manager_interface primary_manager_interface = {
  primary_device_manager_create_source,
  primary_device_manager_get_device,
  default_destructor,
};

static void
bind_primary_manager (struct wl_client *client,
                      void             *data,
                      uint32_t          version,
                      uint32_t          id)
{
  struct wl_resource *resource;

  resource = wl_resource_create (client, &gtk_primary_selection_device_manager_interface,
                                 version, id);
  wl_resource_set_implementation (resource, &primary_manager_interface, NULL, NULL);
}

void
meta_wayland_data_device_primary_legacy_manager_init (MetaWaylandCompositor *compositor)
{
  if (wl_global_create (compositor->wayland_display,
			&gtk_primary_selection_device_manager_interface,
			1, NULL, bind_primary_manager) == NULL)
    g_error ("Could not create data_device");
}

void
meta_wayland_data_device_primary_legacy_init (MetaWaylandDataDevicePrimaryLegacy *data_device)
{
  wl_list_init (&data_device->resource_list);
  wl_list_init (&data_device->focus_resource_list);
}

static struct wl_resource *
create_and_send_primary_offer (MetaWaylandDataDevicePrimaryLegacy *data_device,
                               struct wl_resource                 *target)
{
  MetaWaylandDataOffer *offer;
  MetaDisplay *display = meta_get_display ();
  struct wl_resource *resource;
  GList *mimetypes, *l;

  mimetypes = meta_selection_get_mimetypes (meta_display_get_selection (display),
                                            META_SELECTION_PRIMARY);
  if (!mimetypes)
    return NULL;

  offer = meta_wayland_data_offer_primary_legacy_new (target);
  resource = meta_wayland_data_offer_get_resource (offer);

  gtk_primary_selection_device_send_data_offer (target, resource);

  for (l = mimetypes; l; l = l->next)
    gtk_primary_selection_offer_send_offer (resource, l->data);

  g_list_free_full (mimetypes, g_free);

  return resource;
}

void
meta_wayland_data_device_primary_legacy_set_keyboard_focus (MetaWaylandDataDevicePrimaryLegacy *data_device)
{
  MetaWaylandSeat *seat = wl_container_of (data_device, seat, primary_legacy_data_device);
  struct wl_client *focus_client;
  struct wl_resource *data_device_resource;

  focus_client = meta_wayland_keyboard_get_focus_client (seat->keyboard);

  if (focus_client == data_device->focus_client)
    return;

  data_device->focus_client = focus_client;
  move_resources (&data_device->resource_list,
                  &data_device->focus_resource_list);

  if (!focus_client)
    return;

  move_resources_for_client (&data_device->focus_resource_list,
                             &data_device->resource_list,
                             focus_client);

  wl_resource_for_each (data_device_resource, &data_device->focus_resource_list)
    {
      struct wl_resource *offer;
      offer = create_and_send_primary_offer (data_device, data_device_resource);
      gtk_primary_selection_device_send_selection (data_device_resource, offer);
    }
}
