/* EINA - EFL data type library
 * Copyright (C) 2008 Cedric Bail
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library;
 * if not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdlib.h>
#include <string.h>

#ifdef HAVE_EVIL
# include <Evil.h>
#endif

#include "eina_config.h"
#include "eina_private.h"
#include "eina_error.h"
#include "eina_log.h"

/* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */
#include "eina_safety_checks.h"
#include "eina_magic.h"

/*============================================================================*
*                                  Local                                     *
*============================================================================*/

/**
 * @cond LOCAL
 */

typedef struct _Eina_Magic_String Eina_Magic_String;
struct _Eina_Magic_String
{
   Eina_Magic magic;
   Eina_Bool string_allocated;
   const char *string;
};

static int _eina_magic_string_log_dom = -1;

#ifdef ERR
#undef ERR
#endif
#define ERR(...) EINA_LOG_DOM_ERR(_eina_magic_string_log_dom, __VA_ARGS__)

#ifdef DBG
#undef DBG
#endif
#define DBG(...) EINA_LOG_DOM_DBG(_eina_magic_string_log_dom, __VA_ARGS__)

static Eina_Magic_String *_eina_magic_strings = NULL;
static size_t _eina_magic_strings_count = 0;
static size_t _eina_magic_strings_allocated = 0;
static Eina_Bool _eina_magic_strings_dirty = 0;

static int
_eina_magic_strings_sort_cmp(const void *p1, const void *p2)
{
   const Eina_Magic_String *a = p1, *b = p2;
   return a->magic - b->magic;
}

static int
_eina_magic_strings_find_cmp(const void *p1, const void *p2)
{
   Eina_Magic a = (Eina_Magic)p1;
   const Eina_Magic_String *b = p2;
   return a - b->magic;
}

static Eina_Magic_String *
_eina_magic_strings_alloc(void)
{
   size_t idx;

   if (_eina_magic_strings_count == _eina_magic_strings_allocated)
     {
        void *tmp;
        size_t size;

        if (EINA_UNLIKELY(_eina_magic_strings_allocated == 0))
           size = 48;
        else
           size = _eina_magic_strings_allocated + 16;

        tmp = realloc(_eina_magic_strings, sizeof(Eina_Magic_String) * size);
        if (!tmp)
          {
#ifdef _WIN32
             ERR("could not realloc magic_strings from %Iu to %Iu buckets.",
                 _eina_magic_strings_allocated, size);
#else
             ERR("could not realloc magic_strings from %zu to %zu buckets.",
                 _eina_magic_strings_allocated, size);
#endif
             return NULL;
          }

        _eina_magic_strings = tmp;
        _eina_magic_strings_allocated = size;
     }

   idx = _eina_magic_strings_count;
   _eina_magic_strings_count++;
   return _eina_magic_strings + idx;
}

/**
 * @endcond
 */

/*============================================================================*
*                                 Global                                     *
*============================================================================*/

/**
 * @internal
 * @brief Initialize the magic string module.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
 *
 * This function sets up the magic string module of Eina. It is called by
 * eina_init().
 *
 * @see eina_init()
 */
Eina_Bool
eina_magic_string_init(void)
{
   _eina_magic_string_log_dom = eina_log_domain_register
         ("eina_magic_string", EINA_LOG_COLOR_DEFAULT);
   if (_eina_magic_string_log_dom < 0)
     {
        EINA_LOG_ERR("Could not register log domain: eina_magic_string");
        return EINA_FALSE;
     }

   return EINA_TRUE;
}

/**
 * @internal
 * @brief Shut down the magic string module.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
 *
 * This function shuts down the magic string module set up by
 * eina_magic string_init(). It is called by eina_shutdown().
 *
 * @see eina_shutdown()
 */
Eina_Bool
eina_magic_string_shutdown(void)
{
   Eina_Magic_String *ems, *ems_end;

   ems = _eina_magic_strings;
   ems_end = ems + _eina_magic_strings_count;

   for (; ems < ems_end; ems++)
      if (ems->string_allocated)
         free((char *)ems->string);

         free(_eina_magic_strings);
   _eina_magic_strings = NULL;
   _eina_magic_strings_count = 0;
   _eina_magic_strings_allocated = 0;

   eina_log_domain_unregister(_eina_magic_string_log_dom);
   _eina_magic_string_log_dom = -1;

   return EINA_TRUE;
}

/*============================================================================*
*                                   API                                      *
*============================================================================*/

/**
 * @addtogroup Eina_Magic_Group Magic
 *
 * @brief These functions provide runtime type-checking (magic checks)
 * management for projects.
 *
 * C is a weak statically typed language, in other words, it will just
 * check for types during compile time and any cast will make the
 * compiler believe the type is correct.
 *
 * In real world projects we often need to deal with casts, either
 * explicit or implicit by means of @c void*. We also need to resort
 * to casts when doing inheritance in C, as seen in the example below:
 *
 * @code
 * struct base {
 *    int id;
 *    char *name;
 * };
 * int base_id_get(struct base *ptr) {
 *    return ptr->id;
 * }
 *
 * struct subtype {
 *    struct base base;
 *    time_t date;
 * };
 * @endcode
 *
 * It is perfectly valid to use @c {struct subtype} blobs for functions
 * that expect @c {struct base}, since the fields will have the same
 * offset (as base member is the first, at offset 0). We could give
 * the functions the @c {&subtype->base} and avoid the cast, but often
 * we just cast.
 *
 * In any case, we might be safe and check if the given pointer is
 * actually of the expected type. We can do so by using eina_magic,
 * that is nothing more than attaching an unique type identifier to
 * the members and check for it elsewhere.
 *
 * @code
 * #define BASE_MAGIC 0x12345
 * #define SUBTYPE_MAGIC 0x3333
 * struct base {
 *    int id;
 *    char *name;
 *    EINA_MAGIC;
 * };
 * int base_id_get(struct base *ptr) {
 *    if (!EINA_MAGIC_CHECK(ptr, BASE_MAGIC)) {
 *       EINA_MAGIC_FAIL(ptr, BASE_MAGIC);
 *       return -1;
 *    }
 *    return ptr->id;
 * }
 * void base_free(struct base *ptr) {
 *    if (!EINA_MAGIC_CHECK(ptr, BASE_MAGIC)) {
 *       EINA_MAGIC_FAIL(ptr, BASE_MAGIC);
 *       return;
 *    }
 *    EINA_MAGIC_SET(ptr, EINA_MAGIC_NONE);
 *    free(ptr->name);
 *    free(ptr);
 * }
 * struct base *base_new(int id, const char *name) {
 *    struct base *ptr = malloc(sizeof(struct base));
 *    EINA_MAGIC_SET(ptr, BASE_MAGIC);
 *    ptr->id = id;
 *    ptr->name = strdup(name);
 * }
 *
 * struct subtype {
 *    struct base base;
 *    EINA_MAGIC;
 *    time_t date;
 * };
 *
 * int my_init(void) {
 *    eina_init();
 *    eina_magic_string_set(BASE_MAGIC, "base type");
 *    eina_magic_string_set(SUBTYPE_MAGIC, "subtype");
 * }
 * @endcode
 *
 * This code also shows that it is a good practice to set magic to
 * #EINA_MAGIC_NONE before freeing pointer. Sometimes the pointers are
 * in pages that are still live in memory, so kernel will not send
 * SEGV signal to the process and it may go unnoticed that you're
 * using already freed pointers. By setting them to #EINA_MAGIC_NONE
 * you avoid using the bogus pointer any further and gets a nice error
 * message.
 *
 * @{
 */

/**
 * @brief Return the string associated to the given magic identifier.
 *
 * @param magic The magic identifier.
 * @return The string associated to the identifier.
 *
 * This function returns the string associated to @p magic. If none
 * are found, the this function still returns non @c NULL, in this
 * case an identifier such as "(none)", "(undefined)" or
 * "(unknown)". The returned value must not be freed.
 *
 * The following identifiers may be returned whenever magic is
 * invalid, with their meanings:
 *
 *   - (none): no magic was registered exists at all.
 *   - (undefined): magic was registered and found, but no string associated.
 *   - (unknown): magic was not found in the registry.
 */
EAPI const char *
eina_magic_string_get(Eina_Magic magic)
{
   Eina_Magic_String *ems;

   if (!_eina_magic_strings)
      return "(none)";

   if (_eina_magic_strings_dirty)
     {
        qsort(_eina_magic_strings, _eina_magic_strings_count,
              sizeof(Eina_Magic_String), _eina_magic_strings_sort_cmp);
        _eina_magic_strings_dirty = 0;
     }

   ems = bsearch((void *)magic, _eina_magic_strings,
                 _eina_magic_strings_count, sizeof(Eina_Magic_String),
                 _eina_magic_strings_find_cmp);
   if (ems)
      return ems->string ? ems->string : "(undefined)";

   return "(unknown)";
}

/**
 * @brief Set the string associated to the given magic identifier.
 *
 * @param magic The magic identifier.
 * @param magic_name The string associated to the identifier, must not
 *        be @c NULL.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
 *
 * This function sets the string @p magic_name to @p magic. It is not
 * checked if number or string are already set, then you might end
 * with duplicates in that case.
 *
 * @see eina_magic_string_static_set()
 */
EAPI Eina_Bool
eina_magic_string_set(Eina_Magic magic, const char *magic_name)
{
   Eina_Magic_String *ems;

   EINA_SAFETY_ON_NULL_RETURN_VAL(magic_name, EINA_FALSE);

   ems = _eina_magic_strings_alloc();
   if (!ems)
      return EINA_FALSE;

   ems->magic = magic;
   ems->string_allocated = EINA_TRUE;
   ems->string = strdup(magic_name);
   if (!ems->string)
   {
      ERR("could not allocate string '%s'", magic_name);
      _eina_magic_strings_count--;
      return EINA_FALSE;
   }

   _eina_magic_strings_dirty = 1;
   return EINA_TRUE;
}

/**
 * @brief Set the string associated to the given magic identifier.
 *
 * @param magic The magic identifier.
 * @param magic_name The string associated to the identifier, must not be
 *        @c NULL, it will not be duplcated, just referenced thus it must
 *        be live during magic number usage.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
 *
 * This function sets the string @p magic_name to @p magic. It is not
 * checked if number or string are already set, then you might end
 * with duplicates in that case.
 *
 * @see eina_magic_string_set()
 */
EAPI Eina_Bool
eina_magic_string_static_set(Eina_Magic magic, const char *magic_name)
{
   Eina_Magic_String *ems;

   EINA_SAFETY_ON_NULL_RETURN_VAL(magic_name, EINA_FALSE);

   ems = _eina_magic_strings_alloc();
   if (!ems)
      return EINA_FALSE;

   ems->magic = magic;
   ems->string_allocated = EINA_FALSE;
   ems->string = magic_name;

   _eina_magic_strings_dirty = 1;
   return EINA_TRUE;
}

#ifdef eina_magic_fail
# undef eina_magic_fail
#endif

/**
 * @brief Display a message or abort is a magic check failed.
 *
 * @param d The checked data pointer.
 * @param m The magic identifer to check.
 * @param req_m The requested magic identifier to check.
 * @param file The file in which the magic check failed.
 * @param fnc The function in which the magic check failed.
 * @param line The line at which the magic check failed.
 *
 * This function displays an error message if a magic check has
 * failed, using the following logic in the following order:
 * @li If @p d is @c NULL, a message warns about a @c NULL pointer.
 * @li Otherwise, if @p m is equal to #EINA_MAGIC_NONE, a message
 * warns about a handle that was already freed.
 * @li Otherwise, if @p m is equal to @p req_m, a message warns about
 * a handle that is of wrong type.
 * @li Otherwise, a message warns you about ab-using that function...
 *
 * If the environment variable EINA_LOG_ABORT is set, abort() is
 * called and the program stops. It is useful for debugging programs
 * with gdb.
 */
EAPI void
eina_magic_fail(void *d,
                Eina_Magic m,
                Eina_Magic req_m,
                const char *file,
                const char *fnc,
                int line)
{
   if (!d)
      eina_log_print(EINA_LOG_DOMAIN_GLOBAL, EINA_LOG_LEVEL_CRITICAL,
                     file, fnc, line,
                     "*** Eina Magic Check Failed !!!\n"
                     "    Input handle pointer is NULL !\n"
                     "*** NAUGHTY PROGRAMMER!!!\n"
                     "*** SPANK SPANK SPANK!!!\n"
                     "*** Now go fix your code. Tut tut tut!\n"
                     "\n");
   else
   if (m == EINA_MAGIC_NONE)
      eina_log_print(EINA_LOG_DOMAIN_GLOBAL, EINA_LOG_LEVEL_CRITICAL,
                     file, fnc, line,
                     "*** Eina Magic Check Failed !!!\n"
                     "    Input handle has already been freed!\n"
                     "*** NAUGHTY PROGRAMMER!!!\n"
                     "*** SPANK SPANK SPANK!!!\n"
                     "*** Now go fix your code. Tut tut tut!\n"
                     "\n");
   else
   if (m != req_m)
      eina_log_print(EINA_LOG_DOMAIN_GLOBAL, EINA_LOG_LEVEL_CRITICAL,
                     file, fnc, line,
                     "*** Eina Magic Check Failed !!!\n"
                     "    Input handle is wrong type\n"
                     "    Expected: %08x - %s\n"
                     "    Supplied: %08x - %s\n"
                     "*** NAUGHTY PROGRAMMER!!!\n"
                     "*** SPANK SPANK SPANK!!!\n"
                     "*** Now go fix your code. Tut tut tut!\n"
                     "\n",
                     req_m, eina_magic_string_get(req_m),
                     m, eina_magic_string_get(m));
   else
      eina_log_print(EINA_LOG_DOMAIN_GLOBAL, EINA_LOG_LEVEL_CRITICAL,
                     file, fnc, line,
                     "*** Eina Magic Check Failed !!!\n"
                     "    Why did you call me !\n"
                     "*** NAUGHTY PROGRAMMER!!!\n"
                     "*** SPANK SPANK SPANK!!!\n"
                     "*** Now go fix your code. Tut tut tut!\n"
                     "\n");
}

/**
 * @}
 */
