/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/**
 * bonobo-running-context.c: A global running interface
 *
 * Author:
 *	Michael Meeks (michael@helixcode.com)
 *
 * Copyright (C) 2000, Ximian, Inc.
 */
#include <config.h>
#include <stdio.h>
#include <glib/gmain.h>
#include <glib-object.h>

#include <bonobo/bonobo-context.h>
#include <bonobo/bonobo-exception.h>
#include <bonobo/bonobo-event-source.h>
#include <bonobo/bonobo-moniker-util.h>
#include <bonobo/bonobo-running-context.h>
#include <bonobo/bonobo-private.h>
#include <bonobo/bonobo-main.h>
#include <bonobo/bonobo-debug.h>

#define PARENT_TYPE BONOBO_TYPE_OBJECT

/* you may debug by adding item "running" to BONOBO_DEBUG_FLAGS
   environment variable. */

static BonoboObjectClass *bonobo_running_context_parent_class = NULL;

typedef struct {
	gboolean    emitted_last_unref;
	GHashTable *objects;
	GHashTable *keys;
} BonoboRunningInfo;

static BonoboRunningInfo *bonobo_running_info = NULL;
static BonoboObject      *bonobo_running_context = NULL;
static BonoboEventSource *bonobo_running_event_source = NULL;

enum {
	LAST_UNREF,
	LAST_SIGNAL
};

static guint signals [LAST_SIGNAL] = { 0 };

static void
key_free (gpointer name, gpointer dummy1, gpointer user_data)
{
	g_free (name);
}

#ifdef G_ENABLE_DEBUG
static void
bonobo_ri_debug_foreach (gpointer key, gpointer value, gpointer user_data)
{
	CORBA_Object *o = value;
	
	bonobo_debug_print ("", "[%p]:CORBA_Object still running", o);
		
}
#endif

void
bonobo_running_context_shutdown (void)
{
	if (bonobo_running_info) {

		BonoboRunningInfo *ri = bonobo_running_info;

#ifdef G_ENABLE_DEBUG
		if(_bonobo_debug_flags & BONOBO_DEBUG_RUNNING) {
			bonobo_debug_print ("rinfo-start", 
					    "-------------------------------------------------");
			
			bonobo_debug_print ("running-objects", "%d running objects", 
					    g_hash_table_size (ri->objects));
			g_hash_table_foreach (ri->objects,
					      bonobo_ri_debug_foreach, NULL);
			bonobo_debug_print ("rinfo-end", 
					    "-------------------------------------------------");
		}
#endif /* G_ENABLE_DEBUG */
		if (ri->objects)
			g_hash_table_destroy (ri->objects);
		ri->objects = NULL;

		if (ri->keys) {
			g_hash_table_foreach_remove (
				ri->keys, (GHRFunc) key_free, NULL);
			g_hash_table_destroy (ri->keys);
			ri->keys = NULL;
		}
		g_free (ri);
	}
	bonobo_running_info = NULL;
	bonobo_running_context = NULL;
	bonobo_running_event_source = NULL;
}

static void
check_destroy (GObject *object)
{
	bonobo_running_context = NULL;
	bonobo_running_event_source = NULL;
}

static BonoboRunningInfo *
get_running_info_T (gboolean create)
{
	if (!bonobo_running_info && create) {
		bonobo_running_info = g_new (BonoboRunningInfo, 1);
		bonobo_running_info->objects = g_hash_table_new (NULL, NULL);
		bonobo_running_info->keys    = g_hash_table_new (g_str_hash, g_str_equal);
		bonobo_running_info->emitted_last_unref = FALSE;
	}

	return bonobo_running_info;
}

static void
check_empty_T (void)
{
	BonoboRunningInfo *ri = get_running_info_T (FALSE);

	if (!ri || !bonobo_running_context)
		return;

	if (!ri->emitted_last_unref &&
	    (g_hash_table_size (ri->objects) == 0) &&
	    (g_hash_table_size (ri->keys) == 0)) {

		ri->emitted_last_unref = TRUE;
		BONOBO_UNLOCK ();

		g_signal_emit (G_OBJECT (bonobo_running_context),
			       signals [LAST_UNREF], 0);
		g_return_if_fail (bonobo_running_event_source != NULL);

		bonobo_event_source_notify_listeners (
			bonobo_running_event_source,
			"bonobo:last_unref", NULL, NULL);

		BONOBO_LOCK ();
	}
}

#ifndef bonobo_running_context_add_object_T
void
bonobo_running_context_add_object_T (CORBA_Object object)
{
#ifdef G_ENABLE_DEBUG
	if(_bonobo_debug_flags & BONOBO_DEBUG_RUNNING)
		bonobo_running_context_trace_objects_T (object, "local", 0, 0);
	else
#endif /* G_ENABLE_DEBUG */
	{
		BonoboRunningInfo *ri = get_running_info_T (TRUE);

		ri->emitted_last_unref = FALSE;
		g_hash_table_insert (ri->objects, object, object);
	}
}
#endif


#ifndef bonobo_running_context_remove_object_T
void
bonobo_running_context_remove_object_T (CORBA_Object object)
{
#ifdef G_ENABLE_DEBUG
	if(_bonobo_debug_flags & BONOBO_DEBUG_RUNNING)
		bonobo_running_context_trace_objects_T (object, "local", 0, 1);
	else
#endif /* G_ENABLE_DEBUG */
	{
		BonoboRunningInfo *ri = get_running_info_T (FALSE);

		if (ri) {
			g_hash_table_remove (ri->objects, object);

			check_empty_T ();
		}
	}
}
#endif

#ifndef bonobo_running_context_ignore_object
void
bonobo_running_context_ignore_object (CORBA_Object object)
{
	BONOBO_LOCK ();
#ifdef G_ENABLE_DEBUG
	if(_bonobo_debug_flags & BONOBO_DEBUG_RUNNING)
		bonobo_running_context_trace_objects_T (object, "local", 0, 2);
	else
#endif /* G_ENABLE_DEBUG */
	{
		BonoboRunningInfo *ri = get_running_info_T (FALSE);

		if (ri)
			g_hash_table_remove (ri->objects, object);
	}
	BONOBO_UNLOCK ();
}
#endif

#ifdef G_ENABLE_DEBUG
static void
_running_context_list_objects (gpointer key,
			       gpointer value,
			       gpointer user_data)
{
	CORBA_Object object = (CORBA_Object) value;
	CORBA_char *type_id;
	CORBA_Environment ev;

	CORBA_exception_init (&ev);
	type_id = ORBit_small_get_type_id (object, &ev);
	if (ev._major != CORBA_NO_EXCEPTION)
		type_id = CORBA_string_dup (
			"<error determining object type id!>");
	CORBA_exception_free (&ev);

	bonobo_debug_print ("Alive: ", "[%p]: %s", object, type_id);
	CORBA_free (type_id);
}
#endif

void          
bonobo_running_context_trace_objects_T (CORBA_Object object,
					const char  *fn,
					int          line,
					int          mode)
{
	BonoboRunningInfo *ri;
#ifdef G_ENABLE_DEBUG
	static const char cmode[][14] = {
		"add_object",
		"remove_object",
		"ignore_object"		
	};
#endif
	ri = get_running_info_T (mode == 0);	

	if (ri) {
		switch (mode) {
		case 0:
			g_hash_table_insert (ri->objects, object, object);
			ri->emitted_last_unref = FALSE;
			break;
		case 1:
			g_hash_table_remove (ri->objects, object);

			check_empty_T ();
			break;
		case 2:
			g_hash_table_remove (ri->objects, object);
			break;
		}

#ifdef G_ENABLE_DEBUG
		if(_bonobo_debug_flags & BONOBO_DEBUG_RUNNING) {
			const char *mode_string = cmode[mode];
			bonobo_debug_print (mode_string, 
					    "[%p]:CORBA_Object %d running objects at %s:%d",
					    object, g_hash_table_size (ri->objects), fn, line);
			g_hash_table_foreach (ri->objects, _running_context_list_objects, NULL);
		}
#endif /* G_ENABLE_DEBUG */
	}
}

static void
impl_Bonobo_RunningContext_addObject (PortableServer_Servant servant,
				      const CORBA_Object     object,
				      CORBA_Environment     *ev)
{
	BONOBO_LOCK ();
	bonobo_running_context_add_object_T (object);
	BONOBO_UNLOCK ();
}

static void
impl_Bonobo_RunningContext_removeObject (PortableServer_Servant servant,
					 const CORBA_Object     object,
					 CORBA_Environment     *ev)
{
	BONOBO_LOCK ();
	bonobo_running_context_remove_object_T (object);
	BONOBO_UNLOCK ();
}

static void
impl_Bonobo_RunningContext_addKey (PortableServer_Servant servant,
				   const CORBA_char      *key,
				   CORBA_Environment     *ev)
{
	char              *key_copy, *old_key;
	BonoboRunningInfo *ri;

	BONOBO_LOCK ();

	ri = get_running_info_T (TRUE);

	old_key = g_hash_table_lookup (ri->keys, key);
	if (old_key) {
		g_free (old_key);
		g_hash_table_remove (ri->keys, key);
	}
	key_copy = g_strdup (key);

	g_hash_table_insert (ri->keys, key_copy, key_copy);

	BONOBO_UNLOCK ();
}

static void
impl_Bonobo_RunningContext_removeKey (PortableServer_Servant servant,
				      const CORBA_char      *key,
				      CORBA_Environment     *ev)
{
	BonoboRunningInfo *ri;
	char              *old_key;

	BONOBO_LOCK ();

	ri = get_running_info_T (FALSE);

	if (ri) {
		old_key = g_hash_table_lookup (ri->keys, key);
		g_free (old_key);
		g_hash_table_remove (ri->keys, key);

		check_empty_T ();
	}

	BONOBO_UNLOCK ();
}

static void
impl_Bonobo_RunningContext_atExitUnref (PortableServer_Servant servant,
					const CORBA_Object     object,
					CORBA_Environment     *ev)
{
	bonobo_running_context_at_exit_unref (object);
}

static void
bonobo_running_context_class_init (BonoboRunningContextClass *klass)
{
	GObjectClass *object_class = (GObjectClass *) klass;
	POA_Bonobo_RunningContext__epv *epv = &klass->epv;

	bonobo_running_context_parent_class = g_type_class_peek_parent (klass);

	((BonoboRunningContextClass *)klass)->last_unref = NULL;

	signals [LAST_UNREF] = g_signal_new (
		"last_unref", G_TYPE_FROM_CLASS (object_class),
		G_SIGNAL_RUN_FIRST,
		G_STRUCT_OFFSET (BonoboRunningContextClass, last_unref),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

	epv->addObject     = impl_Bonobo_RunningContext_addObject;
	epv->removeObject  = impl_Bonobo_RunningContext_removeObject;
	epv->addKey        = impl_Bonobo_RunningContext_addKey;
	epv->removeKey     = impl_Bonobo_RunningContext_removeKey;
	epv->atExitUnref   = impl_Bonobo_RunningContext_atExitUnref;

}

static void 
bonobo_running_context_init (GObject *object)
{
	/* nothing to do */
}

BONOBO_TYPE_FUNC_FULL (BonoboRunningContext, 
		       Bonobo_RunningContext,
		       PARENT_TYPE,
		       bonobo_running_context)

BonoboObject *
bonobo_running_context_new (void)
{
	if (bonobo_running_context) {
		bonobo_object_ref (bonobo_running_context);
		return bonobo_running_context;
	}

	bonobo_running_context = g_object_new (
		bonobo_running_context_get_type (), NULL);

	bonobo_running_event_source = bonobo_event_source_new ();
	bonobo_running_context_ignore_object (
	        BONOBO_OBJREF (bonobo_running_event_source));
	bonobo_event_source_ignore_listeners (bonobo_running_event_source);

	bonobo_object_add_interface (BONOBO_OBJECT (bonobo_running_context),
				     BONOBO_OBJECT (bonobo_running_event_source));

	g_signal_connect (G_OBJECT (bonobo_running_context),
			  "destroy", G_CALLBACK (check_destroy), NULL);

	return bonobo_running_context;
}

BonoboObject *
bonobo_context_running_get (void)
{
	return bonobo_running_context_new ();
}

static void
last_unref_cb (gpointer      context,
	       CORBA_Object  object)
{
	bonobo_object_release_unref (object, NULL);
}

void 
bonobo_running_context_at_exit_unref (CORBA_Object object)
{
	CORBA_Environment ev;
	CORBA_Object obj_dup;

	CORBA_exception_init (&ev);

	obj_dup = CORBA_Object_duplicate (object, &ev);

	bonobo_running_context_ignore_object (obj_dup);

	if (bonobo_running_context)
		g_signal_connect (G_OBJECT (bonobo_running_context),
				  "last_unref", G_CALLBACK (last_unref_cb),
				  obj_dup);
	
	CORBA_exception_free (&ev);
}

static void
last_unref_exit_cb (gpointer      context,
		    BonoboObject *object)
{
        bonobo_object_unref (object);
	bonobo_main_quit ();
}

void 
bonobo_running_context_auto_exit_unref (BonoboObject *object)
{
	g_return_if_fail (object != NULL);
	g_return_if_fail (BONOBO_IS_OBJECT (object));

	bonobo_running_context_ignore_object (BONOBO_OBJREF (object));

	if (bonobo_running_context)
		g_signal_connect (G_OBJECT (bonobo_running_context),
				  "last_unref",
				  G_CALLBACK (last_unref_exit_cb),
				  object);
}



syntax highlighted by Code2HTML, v. 0.9.1