/* This is -*- C -*- */
/* vim: set sw=2: */
/* $Id: guppi-data-socket.c,v 1.5 2002/01/14 05:01:17 trow Exp $ */

/*
 * guppi-data-socket.c
 *
 * Copyright (C) 2001 The Free Software Foundation
 *
 * Developed by Jon Trowbridge <trow@gnu.org>
 *
 * 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 "guppi-data-socket.h"

#include <guppi-memory.h>
#include <guppi-debug.h>


static GtkObjectClass *parent_class = NULL;

enum {
  CHANGED,
  PLUGGED,
  LAST_SIGNAL
};

static guint guppi_data_socket_signals[LAST_SIGNAL] = { 0 };

static guint next_group_id = 100;
static GHashTable *socket_hash = NULL;

static GHashTable *
get_socket_hash (void)
{
  if (socket_hash == NULL) {
    socket_hash = g_hash_table_new (NULL, NULL);
  }
  return socket_hash;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

typedef struct _GuppiDataSocketPrivate GuppiDataSocketPrivate;
struct _GuppiDataSocketPrivate {

  guint group_id;

  GuppiData *data;

  guint changed_handler;

  GuppiDataSocketTypeFn type_fn;
  GuppiDataSocketDestroyFn destroy_fn;
  GuppiDataSocketDupFn dup_fn;
  gpointer closure;
};


static void
guppi_data_socket_finalize (GtkObject *obj)
{
  GuppiDataSocket *x = GUPPI_DATA_SOCKET(obj);

  g_hash_table_remove (get_socket_hash (), x);

  if (x->priv->destroy_fn)
    x->priv->destroy_fn (x->priv->closure);

  if (x->priv->changed_handler) {
    g_assert (x->priv->data != NULL);
    gtk_signal_disconnect (GTK_OBJECT (x->priv->data), x->priv->changed_handler);
    x->priv->changed_handler = 0;
  }

  guppi_unref0 (x->priv->data);

  g_free (x->priv);
  x->priv = NULL;

  guppi_finalized (obj);

  if (parent_class->finalize)
    parent_class->finalize (obj);
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static void
plug_cb (gpointer key, gpointer val, gpointer closure)
{
  GuppiDataSocket *sock = key;
  GuppiDataSocket *plugged_sock = closure;

  if (plugged_sock != sock && plugged_sock->priv->group_id == sock->priv->group_id) {
    guppi_data_socket_set_data (sock, plugged_sock->priv->data);
  }
}

static void
plugged (GuppiDataSocket *sock, GuppiData *new_data, GuppiData *old_data)
{
  static gboolean locked = FALSE;

  if (locked)
    return;

  locked = TRUE;

  if (sock->priv->group_id != 0) {
    g_hash_table_foreach (get_socket_hash (), plug_cb, sock);
  }

  locked = FALSE;
}

static void
guppi_data_socket_class_init (GuppiDataSocketClass *klass)
{
  GtkObjectClass *object_class = (GtkObjectClass *)klass;

  parent_class = gtk_type_class (GTK_TYPE_OBJECT);

  klass->plugged = plugged;
  object_class->finalize = guppi_data_socket_finalize;

  guppi_data_socket_signals[CHANGED] = 
    gtk_signal_new ("changed",
                    GTK_RUN_FIRST,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GuppiDataSocketClass, changed),
                    gtk_marshal_NONE__POINTER,
		    GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);

  guppi_data_socket_signals[PLUGGED] =
    gtk_signal_new ("plugged",
                    GTK_RUN_FIRST,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GuppiDataSocketClass, plugged),
                    gtk_marshal_NONE__POINTER_POINTER,
		    GTK_TYPE_NONE, 1, GTK_TYPE_POINTER, GTK_TYPE_POINTER);

  gtk_object_class_add_signals (object_class, guppi_data_socket_signals, LAST_SIGNAL);
}

static void
guppi_data_socket_init (GuppiDataSocket *obj)
{
  obj->priv = g_new0 (GuppiDataSocketPrivate, 1);
}

GtkType
guppi_data_socket_get_type (void)
{
  static GtkType guppi_data_socket_type = 0;
  if (!guppi_data_socket_type) {
    static const GtkTypeInfo guppi_data_socket_info = {
      "GuppiDataSocket",
      sizeof (GuppiDataSocket),
      sizeof (GuppiDataSocketClass),
      (GtkClassInitFunc)guppi_data_socket_class_init,
      (GtkObjectInitFunc)guppi_data_socket_init,
      NULL, NULL, (GtkClassInitFunc)NULL
    };
    guppi_data_socket_type = gtk_type_unique (GTK_TYPE_OBJECT, &guppi_data_socket_info);
  }
  return guppi_data_socket_type;
}

void
guppi_data_socket_construct (GuppiDataSocket *sock,
			     GuppiDataSocketTypeFn type_fn,
			     GuppiDataSocketDestroyFn destroy_fn,
			     GuppiDataSocketDupFn dup_fn,
			     gpointer closure)
{
  g_return_if_fail (GUPPI_IS_DATA_SOCKET (sock));
  g_return_if_fail (sock->priv->group_id == 0);

  sock->priv->group_id   = next_group_id;
  ++next_group_id;

  sock->priv->type_fn    = type_fn;
  sock->priv->destroy_fn = destroy_fn;
  sock->priv->dup_fn     = dup_fn;
  sock->priv->closure    = closure;

  g_hash_table_insert (get_socket_hash (), sock, sock);
}

GuppiDataSocket *
guppi_data_socket_new (void)
{
  GuppiDataSocket *sock;

  sock = GUPPI_DATA_SOCKET (guppi_type_new (guppi_data_socket_get_type ()));
  guppi_data_socket_construct (sock, NULL, NULL, NULL, NULL);

  return sock;
}

static gboolean
our_type_check_fn (GuppiDataSocket *sock, GuppiData *data, gpointer closure)
{
  GtkType accepted_type, data_type;

  if (data == NULL)
    return TRUE;

  accepted_type = (GtkType) GPOINTER_TO_UINT (closure);
  data_type = GTK_OBJECT_TYPE (data);

  return gtk_type_is_a (data_type, accepted_type);
}

GuppiDataSocket *
guppi_data_socket_new_by_type (GtkType type)
{
  GuppiDataSocket *sock;

  g_return_val_if_fail (type != 0, NULL);

  sock = GUPPI_DATA_SOCKET (guppi_type_new (guppi_data_socket_get_type ()));
  guppi_data_socket_construct (sock, our_type_check_fn, NULL, NULL, GUINT_TO_POINTER (type));

  return sock;
}

GuppiDataSocket *
guppi_data_socket_new_full (GuppiDataSocketTypeFn type_fn,
			    GuppiDataSocketDestroyFn destroy_fn,
			    GuppiDataSocketDupFn dup_fn,
			    gpointer closure)
{
  GuppiDataSocket *sock;

  sock = GUPPI_DATA_SOCKET (guppi_type_new (guppi_data_socket_get_type ()));
  guppi_data_socket_construct (sock, type_fn, destroy_fn, dup_fn, closure);

  return sock;
}

GuppiData *
guppi_data_socket_get_data (GuppiDataSocket *sock)
{
  g_return_val_if_fail (GUPPI_IS_DATA_SOCKET (sock), NULL);

  return sock->priv->data;
}

gboolean
guppi_data_socket_try_data (GuppiDataSocket *sock, GuppiData *data)
{
  g_return_val_if_fail (GUPPI_IS_DATA_SOCKET (sock), FALSE);
  g_return_val_if_fail (data == NULL || GUPPI_IS_DATA (data), FALSE);

  if (sock->priv->type_fn)
    return sock->priv->type_fn (sock, data, sock->priv->closure);

  return TRUE;
}

static void
changed_cb (GuppiData *d, gpointer sock)
{
  gtk_signal_emit (GTK_OBJECT (sock), guppi_data_socket_signals[CHANGED]);
}

void
guppi_data_socket_set_data (GuppiDataSocket *sock, GuppiData *data)
{
  g_return_if_fail (GUPPI_IS_DATA_SOCKET (sock));
  g_return_if_fail (data == NULL || GUPPI_IS_DATA (data));

  if (sock->priv->type_fn && !sock->priv->type_fn (sock, data, sock->priv->closure)) {
    return;
  }

  if (data != sock->priv->data) {

    GuppiData *old_data = sock->priv->data;

    if (sock->priv->data) {

      if (sock->priv->changed_handler) {
	gtk_signal_disconnect (GTK_OBJECT (sock->priv->data), sock->priv->changed_handler);
	sock->priv->changed_handler = 0;
      }					 
    }


    sock->priv->data = data;
    guppi_ref (sock->priv->data);

    if (sock->priv->data) {
      sock->priv->changed_handler =
	gtk_signal_connect (GTK_OBJECT (sock->priv->data),
			    "changed",
			    GTK_SIGNAL_FUNC (changed_cb),
			    sock);
    }

    gtk_signal_emit (GTK_OBJECT (sock), guppi_data_socket_signals[PLUGGED], data, old_data);
    guppi_unref (old_data);

  }
}

void
guppi_data_socket_connect (GuppiDataSocket *master, GuppiDataSocket *slave)
{
  g_return_if_fail (GUPPI_IS_DATA_SOCKET (master));
  g_return_if_fail (GUPPI_IS_DATA_SOCKET (slave));

  if (master->priv->group_id == slave->priv->group_id)
    return;

  if (slave->priv->destroy_fn)
    slave->priv->destroy_fn (slave->priv->closure);

  slave->priv->type_fn    = master->priv->type_fn;
  slave->priv->destroy_fn = master->priv->destroy_fn;
  slave->priv->dup_fn     = master->priv->dup_fn;

  if (master->priv->dup_fn)
    slave->priv->closure = master->priv->dup_fn (master->priv->closure);
  else
    slave->priv->closure = master->priv->closure;

  /* Set group id temporarily to zero, to insure that no other GuppiDataSocket
     gets a plugged signal when we set the data. */
  slave->priv->group_id   = 0;
  
  guppi_data_socket_set_data (slave, master->priv->data);

  /* Now that the master and the slave's data are in sync, we can give the
     slave the master's group id. */
  slave->priv->group_id = master->priv->group_id;
}

xmlNodePtr
guppi_data_socket_export_xml (GuppiDataSocket *sock, GuppiXMLDocument *doc)
{
  g_return_val_if_fail (GUPPI_IS_DATA_SOCKET (sock), NULL);
  g_return_val_if_fail (doc != NULL, NULL);

  return xmlNewNode (doc->ns, "DataSocket");
}

gboolean
guppi_data_socket_import_xml (GuppiDataSocket *sock, GuppiXMLDocument *doc, xmlNodePtr node)
{
  g_return_val_if_fail (GUPPI_IS_DATA_SOCKET (sock), FALSE);
  g_return_val_if_fail (doc != NULL, FALSE);
  g_return_val_if_fail (node != NULL, FALSE);

  guppi_FIXME ();

  return TRUE;
}


syntax highlighted by Code2HTML, v. 0.9.1