/* EINA - EFL data type library
 * Copyright (C) 2012 - ProFUSION embedded systems
 *
 * 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>

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

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

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

/**
 * @cond LOCAL
 */

static const char EINA_MAGIC_INARRAY_STR[] = "Eina Inline Array";
static const char EINA_MAGIC_INARRAY_ITERATOR_STR[] = "Eina Inline Array Iterator";
static const char EINA_MAGIC_INARRAY_ACCESSOR_STR[] = "Eina Inline Array Accessor";

typedef struct _Eina_Iterator_Inarray Eina_Iterator_Inarray;
typedef struct _Eina_Accessor_Inarray Eina_Accessor_Inarray;

struct _Eina_Iterator_Inarray
{
   Eina_Iterator iterator;
   const Eina_Inarray *array;
   unsigned int pos;
   EINA_MAGIC
};

struct _Eina_Accessor_Inarray
{
   Eina_Accessor accessor;
   const Eina_Inarray *array;
   EINA_MAGIC
};

static int _eina_inarray_log_dom = -1;

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

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

#define EINA_MAGIC_CHECK_INARRAY(d, ...)                \
  do                                                    \
    {                                                   \
       if (!EINA_MAGIC_CHECK(d, EINA_MAGIC_INARRAY))    \
         {                                              \
            EINA_MAGIC_FAIL(d, EINA_MAGIC_INARRAY);     \
            return __VA_ARGS__;                         \
         }                                              \
    }                                                   \
  while(0)

#define EINA_MAGIC_CHECK_INARRAY_ITERATOR(d, ...)               \
  do                                                            \
    {                                                           \
       if (!EINA_MAGIC_CHECK(d, EINA_MAGIC_INARRAY_ITERATOR))   \
         {                                                      \
            EINA_MAGIC_FAIL(d, EINA_MAGIC_INARRAY_ITERATOR);    \
            return __VA_ARGS__;                                 \
         }                                                      \
    }                                                           \
  while(0)

#define EINA_MAGIC_CHECK_INARRAY_ACCESSOR(d, ...)               \
  do                                                            \
    {                                                           \
       if (!EINA_MAGIC_CHECK(d, EINA_MAGIC_INARRAY_ACCESSOR))   \
         {                                                      \
            EINA_MAGIC_FAIL(d, EINA_MAGIC_INARRAY_ACCESSOR);    \
            return __VA_ARGS__;                                 \
         }                                                      \
    }                                                           \
  while(0)

static void
_eina_inarray_setup(Eina_Inarray *array, unsigned int member_size, unsigned int step)
{
   EINA_MAGIC_SET(array, EINA_MAGIC_INARRAY);
   array->version = EINA_ARRAY_VERSION;
   array->member_size = member_size;
   array->len = 0;
   array->max = 0;
   array->step = (step > 0) ? step : 32;
   array->members = NULL;
}

static Eina_Bool
_eina_inarray_resize(Eina_Inarray *array, unsigned int new_size)
{
   unsigned int new_max;
   void *tmp;

   if (new_size < array->max) /* don't change this behaviour as eina_inarray_pop rely on it */
     return EINA_TRUE;

   if (new_size % array->step == 0)
     new_max = new_size;
   else
     new_max = ((new_size / array->step) + 1) * array->step;

   tmp = realloc(array->members, new_max * array->member_size);
   if ((!tmp) && (new_max > 0)) return EINA_FALSE;

   array->members = tmp;
   array->max = new_max;
   return EINA_TRUE;
}

static inline void *
_eina_inarray_get(const Eina_Inarray *array, unsigned int position)
{
   unsigned int offset = position * array->member_size;
   return (unsigned char *)array->members + offset;
}

static int
_eina_inarray_search(const Eina_Inarray *array, const void *data, Eina_Compare_Cb compare)
{
   unsigned int found, pos=-1;
   void *p;

   if (array->len == 0)
     return -1;

   for (pos = 0; pos < array->len; ++pos)
     {
        p = _eina_inarray_get(array, pos);
        found = compare(data, p);
        if (found == 0)
          break;
     }
   if (pos < array->len)
     return pos;
   else
     return -1;
}

static unsigned int
_eina_inarray_search_sorted_near(const Eina_Inarray *array, const void *data, Eina_Compare_Cb compare, int *cmp)
{
   unsigned int start, last, middle;

   if (array->len == 0)
     {
        *cmp = -1;
        return 0;
     }
   else if (array->len == 1)
     {
        *cmp = compare(data, array->members);
        return 0;
     }

   start = 0;
   last = array->len - 1; /* inclusive */
   do
     {
        void *p;
        middle = start + (last - start) / 2; /* avoid overflow */
        p = _eina_inarray_get(array, middle);
        *cmp = compare(data, p);
        if (*cmp == 0)
          return middle;
        else if (*cmp > 0)
          start = middle + 1;
        else if (middle > 0)
          last = middle - 1;
        else
          break;
     }
   while (start <= last);
   return middle;
}


static Eina_Bool
_eina_inarray_iterator_next(Eina_Iterator_Inarray *it, void **data)
{
   EINA_MAGIC_CHECK_INARRAY_ITERATOR(it, EINA_FALSE);

   if (it->pos >= it->array->len)
     return EINA_FALSE;

   *data = _eina_inarray_get(it->array, it->pos);
   it->pos++;

   return EINA_TRUE;
}

static Eina_Bool
_eina_inarray_iterator_prev(Eina_Iterator_Inarray *it, void **data)
{
   EINA_MAGIC_CHECK_INARRAY_ITERATOR(it, EINA_FALSE);

   if (it->pos == 0)
     return EINA_FALSE;

   it->pos--;
   *data = _eina_inarray_get(it->array, it->pos);

   return EINA_TRUE;
}

static Eina_Inarray *
_eina_inarray_iterator_get_container(Eina_Iterator_Inarray *it)
{
   EINA_MAGIC_CHECK_INARRAY_ITERATOR(it, NULL);
   return (Eina_Inarray *)it->array;
}

static void
_eina_inarray_iterator_free(Eina_Iterator_Inarray *it)
{
   EINA_MAGIC_CHECK_INARRAY_ITERATOR(it);
   MAGIC_FREE(it);
}

static Eina_Bool
_eina_inarray_accessor_get_at(Eina_Accessor_Inarray *it, unsigned int pos, void **data)
{
   EINA_MAGIC_CHECK_INARRAY_ACCESSOR(it, EINA_FALSE);

   if (pos >= it->array->len)
     return EINA_FALSE;

   *data = _eina_inarray_get(it->array, pos);
   return EINA_TRUE;
}

static Eina_Inarray *
_eina_inarray_accessor_get_container(Eina_Accessor_Inarray *it)
{
   EINA_MAGIC_CHECK_INARRAY_ACCESSOR(it, NULL);
   return (Eina_Inarray *)it->array;
}

static void
_eina_inarray_accessor_free(Eina_Accessor_Inarray *it)
{
   EINA_MAGIC_CHECK_INARRAY_ACCESSOR(it);
   MAGIC_FREE(it);
}

/**
 * @endcond
 */


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

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

#define EMS(n) eina_magic_string_static_set(n, n ## _STR)
   EMS(EINA_MAGIC_INARRAY);
   EMS(EINA_MAGIC_INARRAY_ITERATOR);
   EMS(EINA_MAGIC_INARRAY_ACCESSOR);
#undef EMS

   return EINA_TRUE;
}

/**
 * @internal
 * @brief Shut down the inline array module.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
 *
 * This function shuts down the inline array module set up by
 * eina_inarray_init(). It is called by eina_shutdown().
 *
 * @see eina_shutdown()
 */
Eina_Bool
eina_inarray_shutdown(void)
{
   eina_log_domain_unregister(_eina_inarray_log_dom);
   _eina_inarray_log_dom = -1;
   return EINA_TRUE;
}

/*============================================================================*
*                                   API                                      *
*============================================================================*/
EAPI Eina_Inarray *
eina_inarray_new(unsigned int member_size, unsigned int step)
{
   Eina_Inarray *ret;

   EINA_SAFETY_ON_TRUE_RETURN_VAL(member_size == 0, NULL);

   ret = malloc(sizeof(*ret));
   if (!ret) return NULL;
   _eina_inarray_setup(ret, member_size, step);
   return ret;
}

EAPI void
eina_inarray_free(Eina_Inarray *array)
{
   if (!array)
     return;

   EINA_MAGIC_CHECK_INARRAY(array);
   free(array->members);
   free(array);
}

EAPI void
eina_inarray_step_set(Eina_Inarray *array,
		      unsigned int sizeof_eina_inarray,
		      unsigned int member_size,
		      unsigned int step)
{
   EINA_SAFETY_ON_NULL_RETURN(array);
   EINA_SAFETY_ON_TRUE_RETURN(member_size == 0);

   if (sizeof (Eina_Inarray) != sizeof_eina_inarray)
     {
        ERR("Unknown Eina_Inarray size ! Got %i, expected %i\n",
            sizeof_eina_inarray,
            (int) sizeof (Eina_Inarray));
        /* Force memory to zero to provide a small layer of security */
        memset(array, 0, sizeof_eina_inarray);
        return;
     }

   _eina_inarray_setup(array, member_size, step);
}

EAPI void
eina_inarray_flush(Eina_Inarray *array)
{
   EINA_MAGIC_CHECK_INARRAY(array);
   free(array->members);
   array->len = 0;
   array->max = 0;
   array->members = NULL;
}

EAPI Eina_Bool
eina_inarray_resize(Eina_Inarray *array, unsigned int new_size)
{
   Eina_Bool r;
   EINA_MAGIC_CHECK_INARRAY(array, EINA_FALSE);

   r = _eina_inarray_resize(array, new_size);
   if(!r) return EINA_FALSE;

   array->len = new_size;
   return EINA_TRUE;
}

EAPI int
eina_inarray_push(Eina_Inarray *array, const void *data)
{
   void *p;

   EINA_MAGIC_CHECK_INARRAY(array, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(data, -1);

   if (!_eina_inarray_resize(array, array->len + 1))
     return -1;

   p = _eina_inarray_get(array, array->len);
   memcpy(p, data, array->member_size);

   array->len++;
   return array->len - 1;
}

EAPI void *
eina_inarray_grow(Eina_Inarray *array, unsigned int size)
{
   void *p;

   EINA_MAGIC_CHECK_INARRAY(array, NULL);
   if (!size) return NULL;

   if (!_eina_inarray_resize(array, array->len + size))
     return NULL;

   p = _eina_inarray_get(array, array->len);
   array->len += size;

   return p;
}

EAPI int
eina_inarray_insert(Eina_Inarray *array, const void *data, Eina_Compare_Cb compare)
{
   const unsigned char *itr, *itr_end;
   unsigned int sz;

   EINA_MAGIC_CHECK_INARRAY(array, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(data, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(compare, -1);

   sz = array->member_size;
   itr = array->members;
   itr_end = itr + array->len * sz;
   for (; itr < itr_end; itr += sz)
     {
      unsigned int offset, position;
      int cmp = compare(itr, data);
      if (cmp <= 0)
        continue;

      offset = itr - (unsigned char *)array->members;
      position = offset / sz;
      if (!eina_inarray_insert_at(array, position, data))
        return -1;
      return position;
   }
   return eina_inarray_push(array, data);
}

EAPI int
eina_inarray_insert_sorted(Eina_Inarray *array, const void *data, Eina_Compare_Cb compare)
{
   unsigned int pos;
   int cmp;

   EINA_MAGIC_CHECK_INARRAY(array, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(data, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(compare, -1);

   pos = _eina_inarray_search_sorted_near(array, data, compare, &cmp);
   if (cmp > 0)
     pos++;

   if (!eina_inarray_insert_at(array, pos, data))
     return -1;
   return pos;
}

EAPI int
eina_inarray_remove(Eina_Inarray *array, const void *data)
{
   const unsigned char *itr, *itr_end;
   unsigned int position, sz;

   EINA_MAGIC_CHECK_INARRAY(array, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(data, -1);

   sz = array->member_size;
   if ((data >= array->members) &&
       (data < _eina_inarray_get(array, array->len)))
     {
        unsigned int offset = ((unsigned char *)data -
                               (unsigned char *)array->members);
        position = offset / sz;
        goto found;
     }

   itr = array->members;
   itr_end = itr + array->len * sz;
   for (; itr < itr_end; itr += sz)
     {
        if (memcmp(data, itr, sz) == 0)
          {
             unsigned int offset = itr - (unsigned char *)array->members;
             position = offset / sz;
             goto found;
          }
     }
   return -1;

found:
   if (!eina_inarray_remove_at(array, position))
     return -1;
   return position;
}

EAPI void *
eina_inarray_pop(Eina_Inarray *array)
{
   EINA_MAGIC_CHECK_INARRAY(array, NULL);
   if (array->len == 0) return NULL;
   if (!_eina_inarray_resize(array, array->len - 1))
     return NULL;
   array->len--;
   return _eina_inarray_get(array, array->len);
}

EAPI void *
eina_inarray_nth(const Eina_Inarray *array, unsigned int position)
{
   EINA_MAGIC_CHECK_INARRAY(array, NULL);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(position >= array->len, NULL);
   return _eina_inarray_get(array, position);
}

EAPI Eina_Bool
eina_inarray_insert_at(Eina_Inarray *array, unsigned int position, const void *data)
{
   unsigned int sz;
   unsigned char *p;

   EINA_MAGIC_CHECK_INARRAY(array, EINA_FALSE);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(position > array->len, EINA_FALSE);

   if (!_eina_inarray_resize(array, array->len + 1))
     return EINA_FALSE;

   p = _eina_inarray_get(array, position);
   sz = array->member_size;
   if (array->len > position)
     memmove(p + sz, p, (array->len - position) * sz);
   memcpy(p, data, sz);

   array->len++;
   return EINA_TRUE;
}

EAPI void *
eina_inarray_alloc_at(Eina_Inarray *array, unsigned int position, unsigned int member_count)
{
   unsigned int sz;
   unsigned char *p;

   EINA_MAGIC_CHECK_INARRAY(array, NULL);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(position > array->len, NULL);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(member_count == 0, NULL);

   if (!_eina_inarray_resize(array, array->len + member_count))
     return NULL;

   p = _eina_inarray_get(array, position);
   sz = array->member_size;
   if (array->len > position)
     memmove(p + member_count * sz, p, (array->len - position) * sz);

   array->len += member_count;
   return p;
}

EAPI Eina_Bool
eina_inarray_replace_at(Eina_Inarray *array, unsigned int position, const void *data)
{
   unsigned char *p;

   EINA_MAGIC_CHECK_INARRAY(array, EINA_FALSE);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(position >= array->len, EINA_FALSE);

   p = _eina_inarray_get(array, position);
   memcpy(p, data, array->member_size);

   return EINA_TRUE;
}

EAPI Eina_Bool
eina_inarray_remove_at(Eina_Inarray *array, unsigned int position)
{
   EINA_MAGIC_CHECK_INARRAY(array, EINA_FALSE);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(position >= array->len, EINA_FALSE);

   if (position + 1 < array->len)
     {
        unsigned int sz = array->member_size;
        unsigned char *p = _eina_inarray_get(array, position);
        memmove(p, p + sz, (array->len - position - 1) * sz);
     }

   if (!_eina_inarray_resize(array, array->len - 1)) return EINA_FALSE; /* should never fail as we reduce the buffer, but just let make compiler happy */
   array->len--;
   return EINA_TRUE;
}

EAPI void
eina_inarray_reverse(Eina_Inarray *array)
{
   size_t sz;
   unsigned char *fwd, *rev, *fwd_end;
   void *tmp;

   EINA_MAGIC_CHECK_INARRAY(array);

   if (array->len < 2)
     return;

   sz = array->member_size;

   tmp = alloca(sz);
   EINA_SAFETY_ON_NULL_RETURN(tmp);

   fwd = array->members;
   fwd_end = fwd + (array->len / 2) * sz;

   rev = fwd + (array->len - 1) * sz;

   for (; fwd < fwd_end; fwd += sz, rev -= sz)
     {
        memcpy(tmp, fwd, sz);
        memcpy(fwd, rev, sz);
        memcpy(rev, tmp, sz);
     }
}

EAPI void
eina_inarray_sort(Eina_Inarray *array, Eina_Compare_Cb compare)
{
   EINA_MAGIC_CHECK_INARRAY(array);
   EINA_SAFETY_ON_NULL_RETURN(compare);
   qsort(array->members, array->len, array->member_size, compare);
}

EAPI int
eina_inarray_search(const Eina_Inarray *array, const void *data, Eina_Compare_Cb compare)
{
   EINA_MAGIC_CHECK_INARRAY(array, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(data, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(compare, -1);
   return _eina_inarray_search(array, data, compare);
}

EAPI int
eina_inarray_search_sorted(const Eina_Inarray *array, const void *data, Eina_Compare_Cb compare)
{
   unsigned int pos;
   int cmp;

   EINA_MAGIC_CHECK_INARRAY(array, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(data, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(compare, -1);

   pos = _eina_inarray_search_sorted_near(array, data, compare, &cmp);
   if (cmp == 0)
     return pos;
   return -1;
}

EAPI Eina_Bool
eina_inarray_foreach(const Eina_Inarray *array, Eina_Each_Cb function, const void *user_data)
{
   unsigned char *itr, *itr_end;
   unsigned int sz;
   Eina_Bool ret = EINA_TRUE;

   EINA_MAGIC_CHECK_INARRAY(array, EINA_FALSE);
   EINA_SAFETY_ON_NULL_RETURN_VAL(function, EINA_FALSE);

   sz = array->member_size;
   itr = array->members;
   itr_end = itr + array->len * sz;
   for (; (itr < itr_end) && (ret); itr += sz)
     ret = function(array, itr, (void *)user_data);
   return ret;
}

EAPI int
eina_inarray_foreach_remove(Eina_Inarray *array, Eina_Each_Cb match, const void *user_data)
{
   unsigned int i = 0, count = 0;

   EINA_MAGIC_CHECK_INARRAY(array, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(match, -1);

   while (i < array->len)
     {
        void *p = _eina_inarray_get(array, i);
        if (match(array, p, (void *)user_data) == EINA_FALSE)
          {
             i++;
             continue;
          }

        eina_inarray_remove_at(array, i);
        count++;
     }

   return count;
}

EAPI unsigned int
eina_inarray_count(const Eina_Inarray *array)
{
   EINA_MAGIC_CHECK_INARRAY(array, 0);
   return array->len;
}

EAPI Eina_Iterator *
eina_inarray_iterator_new(const Eina_Inarray *array)
{
   Eina_Iterator_Inarray *it;

   EINA_MAGIC_CHECK_INARRAY(array, NULL);

   it = calloc(1, sizeof(Eina_Iterator_Inarray));
   if (!it) return NULL;

   EINA_MAGIC_SET(it,            EINA_MAGIC_INARRAY_ITERATOR);
   EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR);

   it->array = array;

   it->iterator.version = EINA_ITERATOR_VERSION;
   it->iterator.next = FUNC_ITERATOR_NEXT(_eina_inarray_iterator_next);
   it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER
     (_eina_inarray_iterator_get_container);
   it->iterator.free = FUNC_ITERATOR_FREE(_eina_inarray_iterator_free);

   return &it->iterator;
}

EAPI Eina_Iterator *
eina_inarray_iterator_reversed_new(const Eina_Inarray *array)
{
   Eina_Iterator_Inarray *it;

   EINA_MAGIC_CHECK_INARRAY(array, NULL);

   it = calloc(1, sizeof(Eina_Iterator_Inarray));
   if (!it) return NULL;

   EINA_MAGIC_SET(it,            EINA_MAGIC_INARRAY_ITERATOR);
   EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR);

   it->array = array;
   it->pos = array->len;

   it->iterator.version = EINA_ITERATOR_VERSION;
   it->iterator.next = FUNC_ITERATOR_NEXT(_eina_inarray_iterator_prev);
   it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER
     (_eina_inarray_iterator_get_container);
   it->iterator.free = FUNC_ITERATOR_FREE(_eina_inarray_iterator_free);

   return &it->iterator;
}

EAPI Eina_Accessor *
eina_inarray_accessor_new(const Eina_Inarray *array)
{
   Eina_Accessor_Inarray *ac;

   EINA_MAGIC_CHECK_INARRAY(array, NULL);

   ac = calloc(1, sizeof(Eina_Accessor_Inarray));
   if (!ac) return NULL;

   EINA_MAGIC_SET(ac,            EINA_MAGIC_INARRAY_ACCESSOR);
   EINA_MAGIC_SET(&ac->accessor, EINA_MAGIC_ACCESSOR);

   ac->array = array;

   ac->accessor.version = EINA_ACCESSOR_VERSION;
   ac->accessor.get_at = FUNC_ACCESSOR_GET_AT(_eina_inarray_accessor_get_at);
   ac->accessor.get_container = FUNC_ACCESSOR_GET_CONTAINER
     (_eina_inarray_accessor_get_container);
   ac->accessor.free = FUNC_ACCESSOR_FREE(_eina_inarray_accessor_free);

   return &ac->accessor;
}
