/*
 * Cogl
 *
 * A Low Level GPU Graphics and Utilities API
 *
 * Copyright (C) 2008,2009,2010 Intel Corporation.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Authors:
 *   Robert Bragg <robert@linux.intel.com>
 */

#ifndef __COGL_OBJECT_PRIVATE_H
#define __COGL_OBJECT_PRIVATE_H

#include <glib.h>

#include "cogl-types.h"
#include "cogl-object.h"
#include "cogl-debug.h"

/* For compatability until all components have been converted */
typedef struct _CoglObjectClass CoglHandleClass;
typedef struct _CoglObject      CoglHandleObject;

/* XXX: sadly we didn't fully consider when we copied the cairo API
 * for _set_user_data that the callback doesn't get a pointer to the
 * instance which is desired in most cases. This means you tend to end
 * up creating micro allocations for the private data just so you can
 * pair up the data of interest with the original instance for
 * identification when it is later destroyed.
 *
 * Internally we use a small hack to avoid needing these micro
 * allocations by actually passing the instance as a second argument
 * to the callback */
typedef void (*CoglUserDataDestroyInternalCallback) (void *user_data,
                                                     void *instance);

typedef struct _CoglObjectClass
{
  GTypeClass base_class;
  const char *name;
  void *virt_free;
  void *virt_unref;
} CoglObjectClass;

#define COGL_OBJECT_N_PRE_ALLOCATED_USER_DATA_ENTRIES 2

typedef struct
{
  CoglUserDataKey *key;
  void *user_data;
  CoglUserDataDestroyInternalCallback destroy;
} CoglUserDataEntry;

/* All Cogl objects inherit from this base object by adding a member:
 *
 *   CoglObject _parent;
 *
 * at the top of its main structure. This structure is initialized
 * when you call _cogl_#type_name#_object_new (new_object);
 */
struct _CoglObject
{
  CoglObjectClass  *klass; /* equivalent to GTypeInstance */

  CoglUserDataEntry user_data_entry[
    COGL_OBJECT_N_PRE_ALLOCATED_USER_DATA_ENTRIES];
  GArray           *user_data_array;
  int               n_user_data_entries;

  unsigned int      ref_count;
};

/* Helper macro to encapsulate the common code for COGL reference
   counted objects */

#ifdef COGL_OBJECT_DEBUG

#define _COGL_OBJECT_DEBUG_NEW(type_name, obj)                          \
  COGL_NOTE (OBJECT, "COGL " G_STRINGIFY (type_name) " NEW   %p %i",    \
             (obj), (obj)->ref_count)

#define _COGL_OBJECT_DEBUG_REF(type_name, object)       G_STMT_START {  \
  CoglObject *__obj = (CoglObject *)object;                             \
  COGL_NOTE (OBJECT, "COGL %s REF %p %i",                               \
             (__obj)->klass->name,                                      \
             (__obj), (__obj)->ref_count);              } G_STMT_END

#define _COGL_OBJECT_DEBUG_UNREF(type_name, object)     G_STMT_START {  \
  CoglObject *__obj = (CoglObject *)object;                             \
  COGL_NOTE (OBJECT, "COGL %s UNREF %p %i",                             \
             (__obj)->klass->name,                                      \
             (__obj), (__obj)->ref_count - 1);          } G_STMT_END

#define COGL_OBJECT_DEBUG_FREE(obj)                                     \
  COGL_NOTE (OBJECT, "COGL %s FREE %p",                                 \
             (obj)->klass->name, (obj))

#else /* !COGL_OBJECT_DEBUG */

#define _COGL_OBJECT_DEBUG_NEW(type_name, obj)
#define _COGL_OBJECT_DEBUG_REF(type_name, obj)
#define _COGL_OBJECT_DEBUG_UNREF(type_name, obj)
#define COGL_OBJECT_DEBUG_FREE(obj)

#endif /* COGL_OBJECT_DEBUG */

#define _COGL_GTYPE_INIT_CLASS(type_name) do {                                   \
  _cogl_##type_name##_class.base_class.g_type = cogl_##type_name##_get_gtype (); \
} while (0)

#define COGL_OBJECT_COMMON_DEFINE_WITH_CODE(TypeName, type_name, code)  \
                                                                        \
CoglObjectClass _cogl_##type_name##_class;                              \
static unsigned long _cogl_object_##type_name##_count;                  \
                                                                        \
static inline void                                                      \
_cogl_object_##type_name##_inc (void)                                   \
{                                                                       \
  _cogl_object_##type_name##_count++;                                   \
}                                                                       \
                                                                        \
static inline void                                                      \
_cogl_object_##type_name##_dec (void)                                   \
{                                                                       \
  _cogl_object_##type_name##_count--;                                   \
}                                                                       \
                                                                        \
static void                                                             \
_cogl_object_##type_name##_indirect_free (CoglObject *obj)              \
{                                                                       \
  _cogl_##type_name##_free ((Cogl##TypeName *) obj);                    \
  _cogl_object_##type_name##_dec ();                                    \
}                                                                       \
                                                                        \
static void                                                             \
_cogl_object_##type_name##_class_init (void)                            \
{                                                                       \
  _cogl_object_##type_name##_count = 0;                                 \
                                                                        \
    if (_cogl_debug_instances == NULL)                                  \
      _cogl_debug_instances =                                           \
        g_hash_table_new (g_str_hash, g_str_equal);                     \
                                                                        \
    _cogl_##type_name##_class.virt_free =                               \
      _cogl_object_##type_name##_indirect_free;                         \
    _cogl_##type_name##_class.virt_unref =                              \
      _cogl_object_default_unref;                                       \
    _cogl_##type_name##_class.name = "Cogl"#TypeName;                   \
                                                                        \
    g_hash_table_insert (_cogl_debug_instances,                         \
                         (void *) _cogl_##type_name##_class.name,       \
                         &_cogl_object_##type_name##_count);            \
                                                                        \
    { code; }                                                           \
}                                                                       \
                                                                        \
static Cogl##TypeName *                                                 \
_cogl_##type_name##_object_new (Cogl##TypeName *new_obj)                \
{                                                                       \
  CoglObject *obj = (CoglObject *)&new_obj->_parent;                    \
  obj->ref_count = 0;                                                   \
  cogl_object_ref (obj);                                                \
  obj->n_user_data_entries = 0;                                         \
  obj->user_data_array = NULL;                                          \
                                                                        \
  obj->klass = &_cogl_##type_name##_class;                              \
  if (!obj->klass->virt_free)                                           \
    {                                                                   \
      _cogl_object_##type_name##_class_init ();                         \
    }                                                                   \
                                                                        \
  _cogl_object_##type_name##_inc ();                                    \
  _COGL_OBJECT_DEBUG_NEW (TypeName, obj);                               \
  return new_obj;                                                       \
}

#define COGL_OBJECT_DEFINE_WITH_CODE_GTYPE(TypeName, type_name, code)   \
                                                                        \
COGL_OBJECT_COMMON_DEFINE_WITH_CODE(TypeName,                           \
                                    type_name,                          \
                                    do { code; } while (0);             \
                                    _COGL_GTYPE_INIT_CLASS (type_name)) \
                                                                        \
gboolean                                                                \
cogl_is_##type_name (void *object)                                      \
{                                                                       \
  CoglObject *obj = object;                                             \
                                                                        \
  if (object == NULL)                                                   \
    return FALSE;                                                       \
                                                                        \
  return obj->klass == &_cogl_##type_name##_class;                      \
}

#define COGL_OBJECT_DEFINE_WITH_CODE(TypeName, type_name, code)         \
                                                                        \
COGL_OBJECT_COMMON_DEFINE_WITH_CODE(TypeName, type_name, code)          \
                                                                        \
gboolean                                                                \
cogl_is_##type_name (void *object)                                      \
{                                                                       \
  CoglObject *obj = object;                                             \
                                                                        \
  if (object == NULL)                                                   \
    return FALSE;                                                       \
                                                                        \
  return obj->klass == &_cogl_##type_name##_class;                      \
}

#define COGL_OBJECT_INTERNAL_DEFINE_WITH_CODE(TypeName, type_name, code) \
                                                                        \
COGL_OBJECT_COMMON_DEFINE_WITH_CODE(TypeName, type_name, code)          \
                                                                        \
gboolean                                                                \
_cogl_is_##type_name (void *object)                                     \
{                                                                       \
  CoglObject *obj = object;                                             \
                                                                        \
  if (object == NULL)                                                   \
    return FALSE;                                                       \
                                                                        \
  return obj->klass == &_cogl_##type_name##_class;                      \
}

#define COGL_OBJECT_DEFINE_DEPRECATED_REF_COUNTING(type_name)   \
                                                                \
void * G_GNUC_DEPRECATED                                        \
cogl_##type_name##_ref (void *object)                           \
{                                                               \
  if (!cogl_is_##type_name (object))                            \
    return NULL;                                                \
                                                                \
  _COGL_OBJECT_DEBUG_REF (TypeName, object);                    \
                                                                \
  cogl_handle_ref (object);                                     \
                                                                \
  return object;                                                \
}                                                               \
                                                                \
void G_GNUC_DEPRECATED                                          \
cogl_##type_name##_unref (void *object)                         \
{                                                               \
  if (!cogl_is_##type_name (object))                            \
    {                                                           \
      g_warning (G_STRINGIFY (cogl_##type_name##_unref)         \
                 ": Ignoring unref of Cogl handle "             \
                 "due to type mismatch");                       \
      return;                                                   \
    }                                                           \
                                                                \
  _COGL_OBJECT_DEBUG_UNREF (TypeName, object);                  \
                                                                \
  cogl_handle_unref (object);                                   \
}

#define COGL_OBJECT_DEFINE(TypeName, type_name)                 \
  COGL_OBJECT_DEFINE_WITH_CODE_GTYPE (TypeName, type_name, (void) 0)

#define COGL_OBJECT_INTERNAL_DEFINE(TypeName, type_name)         \
  COGL_OBJECT_INTERNAL_DEFINE_WITH_CODE (TypeName, type_name, (void) 0)

/* For temporary compatability */
#define COGL_HANDLE_INTERNAL_DEFINE_WITH_CODE(TypeName, type_name, code) \
                                                                         \
COGL_OBJECT_INTERNAL_DEFINE_WITH_CODE (TypeName, type_name, code)        \
                                                                         \
static Cogl##TypeName *                                                  \
_cogl_##type_name##_handle_new (CoglHandle handle)                       \
{                                                                        \
  return _cogl_##type_name##_object_new (handle);                        \
}

#define COGL_HANDLE_DEFINE_WITH_CODE(TypeName, type_name, code)          \
                                                                         \
COGL_OBJECT_DEFINE_WITH_CODE (TypeName, type_name, code)                 \
                                                                         \
static Cogl##TypeName *                                                  \
_cogl_##type_name##_handle_new (CoglHandle handle)                       \
{                                                                        \
  return _cogl_##type_name##_object_new (handle);                        \
}

#define COGL_HANDLE_DEFINE(TypeName, type_name)                 \
  COGL_HANDLE_DEFINE_WITH_CODE (TypeName, type_name, (void) 0)

void
_cogl_object_set_user_data (CoglObject *object,
                            CoglUserDataKey *key,
                            void *user_data,
                            CoglUserDataDestroyInternalCallback destroy);

void
_cogl_object_default_unref (void *obj);

#endif /* __COGL_OBJECT_PRIVATE_H */

