#include "e.h"

/* yes - i know. glibc specific... but i like being able to do my own */
/* backtraces! NB: you need CFLAGS="-rdynamic -g" LDFLAGS="-rdynamic -g" */
#ifdef OBJECT_PARANOIA_CHECK

/* local subsystem functions */
static void _e_object_segv(int sig);

/* local subsystem globals */
static sigjmp_buf _e_object_segv_buf;
#endif

/* externally accessible functions */
E_API void *
e_object_alloc(int size, int type, E_Object_Cleanup_Func cleanup_func)
{
   E_Object *obj;

   obj = calloc(1, size);
   if (!obj) return NULL;
   obj->magic = E_OBJECT_MAGIC;
   obj->type = type;
   obj->references = 1;
   obj->cleanup_func = cleanup_func;
   return obj;
}

static void
_delay_del(void *data)
{
   E_Object *obj = data;

   obj->delay_del_job = NULL;
   if (obj->del_att_func) obj->del_att_func(obj);
   if (obj->del_func) obj->del_func(obj);
   e_object_unref(obj);
}

E_API void
e_object_del(E_Object *obj)
{
   E_OBJECT_CHECK(obj);
   if (obj->deleted) return;
   if (obj->del_delay_func)
     {
        if (obj->ref_debug)
          INF("[%p] DELAY DEL (REF: %d)", obj, obj->references);
        obj->del_delay_func(obj);
        if (!obj->delay_del_job)
          obj->delay_del_job = ecore_job_add(_delay_del, obj);
        obj->deleted = 1;
        return;
     }
   if (obj->ref_debug)
     INF("[%p] DEL (REF: %d)", obj, obj->references);
   obj->deleted = 1;
   if (obj->del_att_func) obj->del_att_func(obj);
   if (obj->del_func) obj->del_func(obj);
   e_object_unref(obj);
}

E_API void
e_object_delay_del_set(E_Object *obj, void *func)
{
   E_OBJECT_CHECK(obj);
   obj->del_delay_func = func;
}

E_API int
e_object_is_del(E_Object *obj)
{
   E_OBJECT_CHECK_RETURN(obj, 1);
   return obj->deleted;
}

E_API void
e_object_del_func_set(E_Object *obj, E_Object_Cleanup_Func del_func)
{
   E_OBJECT_CHECK(obj);
   obj->del_func = del_func;
}

E_API void
e_object_type_set(E_Object *obj, int type)
{
   E_OBJECT_CHECK(obj);
   obj->type = type;
}

E_API void
e_object_free(E_Object *obj)
{
   E_OBJECT_CHECK(obj);
   if (obj->free_att_func) obj->free_att_func(obj);
   obj->free_att_func = NULL;
   obj->walking_list++;
   while (obj->del_fn_list)
     {
        E_Object_Delfn *dfn = (E_Object_Delfn *)obj->del_fn_list;
        if (!dfn->delete_me) dfn->func(dfn->data, obj);
        obj->del_fn_list = eina_inlist_remove(obj->del_fn_list,
                                              EINA_INLIST_GET(dfn));
        free(dfn);
     }
   obj->walking_list--;
   if (obj->references) return;
   /*
    * FIXME:
    * although this is good - if during cleanup the cleanup func calls
    * other generic funcs to do cleanups on the same object... we get bitching.
    * disable for now (the final free of the struct should probably happen after
    * the cleanup func and be done by the object system - set the magic after
    * cleanup :)
    */
#if 0
   obj->magic = E_OBJECT_MAGIC_FREED;
#endif
   obj->cleanup_func(obj);
   obj = NULL;
}

E_API int
e_object_ref(E_Object *obj)
{
   E_OBJECT_CHECK_RETURN(obj, -1);
   obj->references++;
   if (obj->ref_debug)
     INF("[%p] REF (REF: %d)", obj, obj->references);
   return obj->references;
}

E_API int
e_object_unref(E_Object *obj)
{
   int ref;

   E_OBJECT_CHECK_RETURN(obj, -1);
   if (!obj->references) return 0;
   obj->references--;
   ref = obj->references;
   if (obj->ref_debug)
     {
        if ((ref > 1) && (ref < 1000))
          INF("[%p] UNREF (REF: %d)", obj, ref);
        else if (ref == 1)
          INF("[%p] UNREF (REF: %d)", obj, ref);
        else if (!ref)
          INF("[%p] UNREF (REF: %d)", obj, ref);
        else
          CRI("[%p] UNREF (REF: %d)", obj, ref);
     }
   if (obj->references == 0) e_object_free(obj);
   return ref;
}

E_API int
e_object_ref_get(E_Object *obj)
{
   E_OBJECT_CHECK_RETURN(obj, 0);
   return obj->references;
}

#if 0
E_API void
e_bt(void)
{
   int i, trace_num;
   void *trace[128];
   char **messages = NULL;
   trace_num = backtrace(trace, 128);
   messages = backtrace_symbols(trace, trace_num);
   if (messages)
     {
        for (i = 1; i < trace_num; i++)
          {
             int j;

             for (j = 1; j < i; j++)
               putchar(' ');
             printf("%s\n", messages[i]);
          }
        free(messages);
     }
}

#endif

E_API int
e_object_error(E_Object *obj)
{
#ifdef OBJECT_PARANOIA_CHECK
   char buf[4096];
   char bt[8192];
   void *trace[128];
   char **messages = NULL;
   int i, trace_num;

   /* fetch stacktrace */
   trace_num = backtrace(trace, 128);
   messages = backtrace_symbols(trace, trace_num);

   /* build stacktrace */
   bt[0] = 0;
   if (messages)
     {
        for (i = 1; i < trace_num; i++)
          {
             strcat(bt, messages[i]);
             strcat(bt, "\n");
          }
        free(messages);
     }
   /* if NULL obj then dump stacktrace */
   if (!obj)
     {
        snprintf(buf, sizeof(buf),
                 "Object is NULL.\n"
                 "%s",
                 bt);
     }
   /* if obj pointer is non NULL, actually try an access and see if we segv */
   else if (obj)
     {
        struct sigaction act, oact;
        int magic = 0, segv = 0;

        /* setup segv handler */
        act.sa_handler = _e_object_segv;
        act.sa_flags = SA_RESETHAND;
        sigemptyset(&act.sa_mask);
        sigaction(SIGSEGV, &act, &oact);
        /* set a longjump to be within this if statement. only called if we */
        /* segfault */
        if (sigsetjmp(_e_object_segv_buf, 1))
          {
             sigaction(SIGSEGV, &oact, NULL);
             segv = 1;
          }
        else
          {
             /* try access magic value */
             magic = obj->magic;
             /* if pointer is bogus we'd segv and so jump to the if () above */
             /* contents, and thus reset segv handler and set segv flag. */
             /* if not we just continue moving along here and reset handler */
             sigaction(SIGSEGV, &oact, NULL);
          }
        /* if we segfaulted above... */
        if (segv)
          snprintf(buf, sizeof(buf),
                   "Object [%p] is an invalid/garbage pointer.\n"
                   "Segfault successfully avoided.\n"
                   "%s",
                   obj,
                   bt);
        else
          {
             /* valid ram then... if we freed this object before */
             if (magic == E_OBJECT_MAGIC_FREED)
               snprintf(buf, sizeof(buf),
                        "Object [%p] is already freed.\n"
                        "%s",
                        obj,
                        bt);
             /* garbage magic value - pointer to non object */
             else if (magic != E_OBJECT_MAGIC)
               snprintf(buf, sizeof(buf),
                        "Object [%p] has garbage magic (%x).\n"
                        "%s",
                        obj, magic,
                        bt);
             else if (obj->references < 0)
               snprintf(buf, sizeof(buf),
                        "Object [%p] has negative references (%i).\n"
                        "%s",
                        obj, obj->references,
                        bt);
             else if (obj->references > 100)
               snprintf(buf, sizeof(buf),
                        "Object [%p] has unusually high reference count (%i).\n"
                        "%s",
                        obj, obj->references,
                        bt);
             /* it's all ok! */
             else
               {
                  return 0;
               }
          }
     }
   /* display actual error message */
   e_error_message_show("%s", buf);
   return 1;
#else
   if (!obj)
     {
        fprintf(stderr, "ERROR: Object is NULL. Add break point at %s:%d\n",
                __FILE__, __LINE__);
        return 1;
     }
   return 0;
#endif
}

E_API void
e_object_data_set(E_Object *obj, const void *data)
{
   E_OBJECT_CHECK(obj);
   obj->data = (void *)data;
}

E_API void *
e_object_data_get(E_Object *obj)
{
   E_OBJECT_CHECK_RETURN(obj, NULL);
   return obj->data;
}

E_API void
e_object_free_attach_func_set(E_Object *obj, E_Object_Cleanup_Func func)
{
   E_OBJECT_CHECK(obj);
   obj->free_att_func = func;
}

E_API void
e_object_del_attach_func_set(E_Object *obj, E_Object_Cleanup_Func func)
{
   E_OBJECT_CHECK(obj);
   obj->del_att_func = func;
}

E_API void
e_object_ref_debug_set(E_Object *obj, Eina_Bool set)
{
   E_OBJECT_CHECK(obj);
   obj->ref_debug = !!set;
}

E_API void
e_object_delfn_clear(E_Object *obj)
{
   E_Object_Delfn *dfn;

   E_OBJECT_CHECK(obj);
   if (obj->walking_list)
     {
        EINA_INLIST_FOREACH(obj->del_fn_list, dfn)
          {
             dfn->delete_me = 1;
          }
        return;
     }
   while (obj->del_fn_list)
     {
        dfn = (E_Object_Delfn *)obj->del_fn_list;
        obj->del_fn_list = eina_inlist_remove(obj->del_fn_list,
                                              EINA_INLIST_GET(dfn));
        free(dfn);
     }
}

E_API E_Object_Delfn *
e_object_delfn_add(E_Object *obj, void (*func)(void *data, void *obj), void *data)
{
   E_Object_Delfn *dfn;
   E_OBJECT_CHECK_RETURN(obj, NULL);
   dfn = calloc(1, sizeof(E_Object_Delfn));
   if (!dfn) return NULL;
   dfn->func = func;
   dfn->data = data;
   obj->del_fn_list = eina_inlist_append(obj->del_fn_list, EINA_INLIST_GET(dfn));
   return dfn;
}

E_API void
e_object_delfn_del(E_Object *obj, E_Object_Delfn *dfn)
{
   E_OBJECT_CHECK(obj);
   if (obj->walking_list)
     {
        dfn->delete_me = 1;
        return;
     }
   obj->del_fn_list = eina_inlist_remove(obj->del_fn_list, EINA_INLIST_GET(dfn));
   free(dfn);
}

/*
   void
   e_object_breadcrumb_add(E_Object *obj, char *crumb)
   {
   E_OBJECT_CHECK(obj);
   obj->crumbs = eina_list_append(obj->crumbs, strdup(crumb));
   }

   void
   e_object_breadcrumb_del(E_Object *obj, char *crumb)
   {
   Eina_List *l;
   char *key;

   E_OBJECT_CHECK(obj);
   EINA_LIST_FOREACH(obj->crumbs, l, key)
     {
        if (!strcmp(crumb, key))
          {
             free(key);
             obj->crumbs = eina_list_remove_list(obj->crumbs, l);
             return;
          }
     }
   }

   void
   e_object_breadcrumb_debug(E_Object *obj)
   {
   Eina_List *l;
   char *key;

   E_OBJECT_CHECK(obj);
   EINA_LISt_FOREACH(obj->crumbs, l, key)
     printf("CRUMB: %s\n", key);
   }
 */

#ifdef OBJECT_PARANOIA_CHECK
/* local subsystem functions */
static void
_e_object_segv(int sig)
{
   siglongjmp(_e_object_segv_buf, 1);
}

#endif
