/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <string.h>
#include "gtkobject.h"
#include "gtktypeutils.h"


typedef struct _GtkTypeNode GtkTypeNode;

struct _GtkTypeNode
{
  guint type;
  gint init_class;
  gpointer klass;
  GtkTypeInfo type_info;
  GtkTypeNode *parent;
  GList *children;
};


static void  gtk_type_insert       (guint        parent_type,
				    guint        type,
				    GtkTypeInfo *type_info);
static void  gtk_type_class_init   (GtkTypeNode *node);
static void  gtk_type_object_init  (GtkTypeNode *node,
				    gpointer     object);
static guint gtk_type_hash         (guint       *key);
static gint  gtk_type_compare      (guint       *a,
			            guint       *b);
static guint gtk_type_name_hash    (char        *key);
static gint  gtk_type_name_compare (char        *a,
				    char        *b);


static int initialize = TRUE;
static GHashTable *type_hash_table = NULL;
static GHashTable *name_hash_table = NULL;


static void
gtk_type_init ()
{
  if (initialize)
    {
      initialize = FALSE;
      type_hash_table = g_hash_table_new ((GHashFunc) gtk_type_hash,
					  (GCompareFunc) gtk_type_compare);
      name_hash_table = g_hash_table_new ((GHashFunc) gtk_type_name_hash,
					  (GCompareFunc) gtk_type_name_compare);
    }
}

guint
gtk_type_unique (guint        parent_type,
		 GtkTypeInfo *type_info)
{
  static guint next_type = 1;
  guint new_type;

  g_return_val_if_fail (type_info != NULL, 0);

  if (initialize)
    gtk_type_init ();

  new_type = next_type++;
  gtk_type_insert (parent_type, new_type, type_info);

  return new_type;
}

gchar*
gtk_type_name (guint type)
{
  GtkTypeNode *node;

  if (initialize)
    gtk_type_init ();

  node = g_hash_table_lookup (type_hash_table, &type);

  if (node)
    return node->type_info.type_name;

  return NULL;
}

guint
gtk_type_from_name (gchar *name)
{
  GtkTypeNode *node;

  if (initialize)
    gtk_type_init ();

  node = g_hash_table_lookup (name_hash_table, name);

  if (node)
    return node->type;

  return 0;
}

guint
gtk_type_parent (guint type)
{
  GtkTypeNode *node;

  if (initialize)
    gtk_type_init ();

  node = g_hash_table_lookup (type_hash_table, &type);

  if (node && node->parent)
    return node->parent->type;

  return 0;
}

gpointer
gtk_type_class (guint type)
{
  GtkTypeNode *node;

  if (initialize)
    gtk_type_init ();

  node = g_hash_table_lookup (type_hash_table, &type);
  g_return_val_if_fail (node != NULL, NULL);

  if (node->init_class)
    gtk_type_class_init (node);

  return node->klass;
}

gpointer
gtk_type_new (guint type)
{
  GtkTypeNode *node;
  gpointer object;

  if (initialize)
    gtk_type_init ();

  node = g_hash_table_lookup (type_hash_table, &type);
  g_return_val_if_fail (node != NULL, NULL);

  object = g_new0 (guchar, node->type_info.object_size);
  ((GtkObject*) object)->klass = gtk_type_class (type);
  gtk_type_object_init (node, object);

  return object;
}

void
gtk_type_describe_heritage (guint type)
{
  GtkTypeNode *node;
  gint first;

  if (initialize)
    gtk_type_init ();

  node = g_hash_table_lookup (type_hash_table, &type);
  first = TRUE;

  while (node)
    {
      if (first)
	{
	  first = FALSE;
	  g_print ("is a ");
	}

      if (node->type_info.type_name)
	g_print ("%s\n", node->type_info.type_name);
      else
	g_print ("<unnamed type>\n");

      node = node->parent;
    }
}

void
gtk_type_describe_tree (guint type,
			gint  show_size)
{
  static gint indent = 0;
  GtkTypeNode *node;
  GtkTypeNode *child;
  GList *children;
  gint old_indent;
  gint i;

  if (initialize)
    gtk_type_init ();

  node = g_hash_table_lookup (type_hash_table, &type);

  for (i = 0; i < indent; i++)
    g_print (" ");

  if (node->type_info.type_name)
    g_print ("%s", node->type_info.type_name);
  else
    g_print ("<unnamed type>");

  if (show_size)
    g_print (" ( %d bytes )\n", node->type_info.object_size);
  else
    g_print ("\n");

  old_indent = indent;
  indent += 4;

  children = node->children;
  while (children)
    {
      child = children->data;
      children = children->next;

      gtk_type_describe_tree (child->type, show_size);
    }

  indent = old_indent;
}

gint
gtk_type_is_a (guint type,
	       guint is_a_type)
{
  GtkTypeNode *node;

  if (initialize)
    gtk_type_init ();

  node = g_hash_table_lookup (type_hash_table, &type);

  while (node)
    {
      if (node->type == is_a_type)
	return TRUE;
      node = node->parent;
    }

  return FALSE;
}

void
gtk_type_set_arg (GtkObject *object,
		  guint      type,
		  GtkArg    *arg)
{
  GtkTypeNode *node;

  if (initialize)
    gtk_type_init ();

  node = g_hash_table_lookup (type_hash_table, &type);

  if (node->type_info.arg_func)
    (* node->type_info.arg_func) (object, arg);
}

static void
gtk_type_insert (guint        parent_type,
		 guint        type,
		 GtkTypeInfo *type_info)
{
  GtkTypeNode *node;
  GtkTypeNode *parent;

  parent = g_hash_table_lookup (type_hash_table, &parent_type);

  node = g_new (GtkTypeNode, 1);
  node->type = type;
  node->init_class = TRUE;
  node->klass = NULL;
  node->type_info = *type_info;
  node->parent = parent;
  node->children = NULL;

  if (node->parent)
    node->parent->children = g_list_append (node->parent->children, node);

  g_hash_table_insert (type_hash_table, &node->type, node);
  g_hash_table_insert (name_hash_table, node->type_info.type_name, node);
}

static void
gtk_type_class_init (GtkTypeNode *node)
{
  GtkObjectClass *object_class;

  if (node->init_class)
    {
      node->init_class = FALSE;
      node->klass = g_new0 (guchar, node->type_info.class_size);

      if (node->parent)
	{
	  if (node->parent->init_class)
	    gtk_type_class_init (node->parent);

	  memcpy (node->klass, node->parent->klass, node->parent->type_info.class_size);
	}

      object_class = node->klass;
      object_class->type = node->type;

      if (node->type_info.class_init_func)
	(* node->type_info.class_init_func) (node->klass);
    }
}

static void
gtk_type_object_init (GtkTypeNode *node,
		      gpointer     object)
{
  if (node->parent)
    gtk_type_object_init (node->parent, object);

  if (node->type_info.object_init_func)
    (* node->type_info.object_init_func) (object);
}

static guint
gtk_type_hash (guint *key)
{
  return *key;
}

static gint
gtk_type_compare (guint *a,
		  guint *b)
{
  return (*a == *b);
}

static guint
gtk_type_name_hash (char *key)
{
  guint result;

  result = 0;
  while (*key)
    result += (result << 3) + *key++;

  return result;
}

static gint
gtk_type_name_compare (char *a,
		       char *b)
{
  return (strcmp (a, b) == 0);
}
