/* ATK - Accessibility Toolkit
 * Copyright 2001 Sun Microsystems Inc.
 *
 * 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include "atkregistry.h"
#include "atknoopobjectfactory.h"

/**
 * SECTION:atkregistry
 * @Short_description: An object used to store the GType of the
 * factories used to create an accessible object for an object of a
 * particular GType.
 * @Title:AtkRegistry
 *
 * The AtkRegistry is normally used to create appropriate ATK "peers"
 * for user interface components.  Application developers usually need
 * only interact with the AtkRegistry by associating appropriate ATK
 * implementation classes with GObject classes via the
 * atk_registry_set_factory_type call, passing the appropriate GType
 * for application custom widget classes.
 */

static AtkRegistry *default_registry = NULL;

static void              atk_registry_init           (AtkRegistry      *instance,
                                                      AtkRegistryClass *klass);
static void              atk_registry_finalize       (GObject          *instance);
static void              atk_registry_class_init     (AtkRegistryClass *klass);
static AtkRegistry*      atk_registry_new            (void);

static gpointer parent_class = NULL;

GType
atk_registry_get_type (void)
{
  static GType type = 0;

  if (!type)
    {
      static const GTypeInfo info =
      {
        sizeof (AtkRegistryClass),
        (GBaseInitFunc) NULL,                             /* base_init */
        (GBaseFinalizeFunc) NULL,                         /* base_finalize */
        (GClassInitFunc) atk_registry_class_init,         /* class_init */
        (GClassFinalizeFunc) NULL,                        /* class_finalize */
        NULL,                                             /* class_data */
        sizeof (AtkRegistry),                             /* instance size */
        0,                                                /* n_preallocs */
        (GInstanceInitFunc) atk_registry_init,            /* instance init */
        NULL                                              /* value table */
      };

      type = g_type_register_static (G_TYPE_OBJECT, "AtkRegistry", &info, 0);
    }

  return type;
}

static void
atk_registry_class_init (AtkRegistryClass *klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;

  parent_class = g_type_class_peek_parent (klass);

  object_class->finalize = atk_registry_finalize;
}

static void
atk_registry_init (AtkRegistry *instance, AtkRegistryClass *klass)
{
  instance->factory_type_registry = g_hash_table_new ((GHashFunc) NULL, 
                                                      (GEqualFunc) NULL);
  instance->factory_singleton_cache = g_hash_table_new ((GHashFunc) NULL, 
                                                        (GEqualFunc) NULL);
}

static AtkRegistry *
atk_registry_new (void)
{
  GObject *object;

  object = g_object_new (ATK_TYPE_REGISTRY, NULL);

  g_return_val_if_fail (ATK_IS_REGISTRY (object), NULL);

  return (AtkRegistry *) object;
}

static void
atk_registry_finalize (GObject *object)
{
  AtkRegistry *registry = ATK_REGISTRY (object);

  g_hash_table_destroy (registry->factory_type_registry);
  g_hash_table_destroy (registry->factory_singleton_cache);

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

/**
 * atk_registry_set_factory_type:
 * @registry: the #AtkRegistry in which to register the type association
 * @type: an #AtkObject type 
 * @factory_type: an #AtkObjectFactory type to associate with @type.  Must
 * implement AtkObject appropriate for @type.
 *
 * Associate an #AtkObjectFactory subclass with a #GType. Note:
 * The associated @factory_type will thereafter be responsible for
 * the creation of new #AtkObject implementations for instances
 * appropriate for @type.
 **/
void
atk_registry_set_factory_type (AtkRegistry *registry,
                               GType type,
                               GType factory_type)
{
  GType old_type;
  gpointer value;
  AtkObjectFactory *old_factory;

  g_return_if_fail (ATK_IS_REGISTRY (registry));

  value = g_hash_table_lookup (registry->factory_type_registry, 
                                  (gpointer) type);
  old_type = (GType) value;
  if (old_type && old_type != factory_type)
    {
      g_hash_table_remove (registry->factory_type_registry, 
                           (gpointer) type);
      /*
       * If the old factory was created, notify it that it has
       * been replaced, then free it.
       */
      old_factory = g_hash_table_lookup (registry->factory_singleton_cache, 
                                         (gpointer) old_type);
      if (old_factory)
        {
          atk_object_factory_invalidate (old_factory);
          g_type_free_instance ((GTypeInstance *) old_factory);
        }
    }
  g_hash_table_insert (registry->factory_type_registry, 
                       (gpointer) type, 
                       (gpointer) factory_type);
}

/**
 * atk_registry_get_factory_type:
 * @registry: an #AtkRegistry
 * @type: a #GType with which to look up the associated #AtkObjectFactory
 * subclass
 *
 * Provides a #GType indicating the #AtkObjectFactory subclass
 * associated with @type.
 *
 * Returns: a #GType associated with type @type
 **/
GType
atk_registry_get_factory_type (AtkRegistry *registry,
                               GType type)
{
  GType factory_type;
  gpointer value;

  /*
   * look up factory type in first hash;
   * if there isn't an explicitly registered factory type,
   * try inheriting one...
   */
  do {
    value =
        g_hash_table_lookup (registry->factory_type_registry, 
                             (gpointer) type);
    type = g_type_parent (type);
    if (type == G_TYPE_INVALID)
      {
        break;
      }
  } while (value == NULL);

  factory_type = (GType) value;
  return factory_type;
}

/**
 * atk_registry_get_factory:
 * @registry: an #AtkRegistry
 * @type: a #GType with which to look up the associated #AtkObjectFactory
 *
 * Gets an #AtkObjectFactory appropriate for creating #AtkObjects
 * appropriate for @type.
 *
 * Returns: (transfer none): an #AtkObjectFactory appropriate for creating
 * #AtkObjects appropriate for @type.
 **/
AtkObjectFactory*
atk_registry_get_factory (AtkRegistry *registry,
                          GType type)
{
  gpointer factory_pointer = NULL;
  GType factory_type;

  factory_type = atk_registry_get_factory_type (registry, type);

  if (factory_type == G_TYPE_INVALID)
  {
  /* Factory type has not been specified for this object type */
    static AtkObjectFactory* default_factory = NULL;

    if (!default_factory)
      default_factory = atk_no_op_object_factory_new ();

    return default_factory;
  }

  /* ask second hashtable for instance of factory type */
  factory_pointer =
        g_hash_table_lookup (registry->factory_singleton_cache, 
        (gpointer) factory_type);

  /* if there isn't one already, create one and save it */
  if (factory_pointer == NULL)
    {
      factory_pointer = g_type_create_instance (factory_type);
      g_hash_table_insert (registry->factory_singleton_cache,
                           (gpointer) factory_type,
                           factory_pointer);
    }

  return ATK_OBJECT_FACTORY (factory_pointer);
}

/**
 * atk_get_default_registry:
 *
 * Gets a default implementation of the #AtkObjectFactory/type
 * registry.
 * Note: For most toolkit maintainers, this will be the correct
 * registry for registering new #AtkObject factories. Following
 * a call to this function, maintainers may call atk_registry_set_factory_type()
 * to associate an #AtkObjectFactory subclass with the GType of objects
 * for whom accessibility information will be provided.
 *
 * Returns: (transfer full): a default implementation of the
 * #AtkObjectFactory/type registry
 **/
AtkRegistry*
atk_get_default_registry (void)
{
  if (!default_registry)
    {
      default_registry = atk_registry_new ();
    }
  return default_registry;
}
