/* EINA - EFL data type library
 * Copyright (C) 2015 Carsten Haitzler
 *
 * 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/>.
 */

#include <string.h>

#include "eina_debug.h"

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

#ifdef HAVE_VALGRIND
# include <valgrind.h>
# include <memcheck.h>
#endif

#ifdef _WIN32
# include <evil_private.h> /* mmap */
#else
# include <sys/mman.h>
#endif

#ifdef HAVE_MMAP

// custom memory allocators to avoid malloc/free during backtrace handling
// just in case we're inside some signal handler due to mem corruption and
// are inside a malloc/free lock and thus would deadlock ourselves if we
// allocated memory, so implement scratch space just big enough for what we
// need and then some via either a static 8k+4k buffer pair or via a growable
// mmaped mem chunk pair
// implement using mmap so we can grow if needed - unlikelt though
static unsigned char *chunk1 = NULL;
static unsigned char *chunk2 = NULL;
static unsigned char *chunk3 = NULL;
static int chunk1_size = 0;
static int chunk1_num = 0;
static int chunk2_size = 0;
static int chunk2_num = 0;
static int chunk3_size = 0;
static int chunk3_num = 0;

// get a new chunk of "anonymous mmaped memory"
static void *
_eina_debug_chunk_need(int size)
{
   void *ptr;

#ifdef HAVE_VALGRIND
   if (RUNNING_ON_VALGRIND) ptr = malloc(size);
   else
#endif
     {
        ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANON, -1, 0);
        if (ptr == MAP_FAILED) return NULL;
     }
   return ptr;
}

// release a chunk of this mmaped anon mem if we don't need it anymore
static void
_eina_debug_chunk_noneed(void *ptr, int size)
{
#ifdef HAVE_VALGRIND
   if (RUNNING_ON_VALGRIND) free(ptr);
   else
#endif
     munmap(ptr, size);
}

// push a new bit of mem on our growing stack of mem - given our workload,
// we never free anything here, only ever grow new things on this stack
void *
_eina_debug_chunk_push(int size)
{
   void *ptr;

   // no initial chunk1 block - allocate it
   if (!chunk1)
     {
        chunk1 = _eina_debug_chunk_need(8 * 1024);
        if (!chunk1) return NULL;
        chunk1_size = 8 * 1024;
     }
   // round size up to the nearest pointer size for alignment
   size = sizeof(void *) * ((size + sizeof(void *) - 1) / sizeof(void *));
   // if our chunk is too small - grow it
   if ((chunk1_num + size) > chunk1_size)
     {
        // get a new chunk twice as big
        void *newchunk = _eina_debug_chunk_need(chunk1_size * 2);
        if (!newchunk) return NULL;
        // copy content over
        memcpy(newchunk, chunk1, chunk1_num);
        // release old chunk
        _eina_debug_chunk_noneed(chunk1, chunk1_size);
        // switch to our new 2x as big chunk
        chunk1 = newchunk;
        chunk1_size = chunk1_size * 2;
     }
   // get the mem at the top of this stack and return it, then move along
   ptr = chunk1 + chunk1_num;
   chunk1_num += size;
   return ptr;
}

// grow a single existing chunk (we use this for the filename -> path lookup)
void *
_eina_debug_chunk_realloc(int size)
{
   // we have a null/empty second chunk - allocate one
   if (!chunk2)
     {
        chunk2 = _eina_debug_chunk_need(4 * 1024);
        if (!chunk2) return NULL;
        chunk2_size = 4 * 1024;
     }
   // if our chunk is too small - grow it
   if (size > chunk2_size)
     {
        // get a new chunk twice as big
        void *newchunk = _eina_debug_chunk_need(chunk2_size * 2);
        if (!newchunk) return NULL;
        // copy content over
        memcpy(newchunk, chunk2, chunk2_num);
        // release old chunk
        _eina_debug_chunk_noneed(chunk2, chunk2_size);
        // switch to our new 2x as big chunk
        chunk2 = newchunk;
        chunk2_size = chunk2_size * 2;
     }
   // record new size and return chunk ptr as we just re-use it
   chunk2_num = size;
   return chunk2;
}

// grow a single existing chunk (we use this for the filename -> path lookup)
void *
_eina_debug_chunk_tmp_push(int size)
{
   void *ptr;

   // no initial chunk1 block - allocate it
   if (!chunk3)
     {
        chunk3 = _eina_debug_chunk_need(32 * 1024);
        if (!chunk3) return NULL;
        chunk3_size = 32 * 1024;
     }
   // round size up to the nearest pointer size for alignment
   size = sizeof(void *) * ((size + sizeof(void *) - 1) / sizeof(void *));
   // if our chunk is too small - grow it
   if ((chunk3_num + size) > chunk3_size)
     {
        // get a new chunk twice as big
        void *newchunk = _eina_debug_chunk_need(chunk3_size * 2);
        if (!newchunk) return NULL;
        // copy content over
        memcpy(newchunk, chunk3, chunk3_num);
        // release old chunk
        _eina_debug_chunk_noneed(chunk3, chunk3_size);
        // switch to our new 2x as big chunk
        chunk3 = newchunk;
        chunk3_size = chunk3_size * 2;
     }
   // get the mem at the top of this stack and return it, then move along
   ptr = chunk3 + chunk3_num;
   chunk3_num += size;
   return ptr;
}

void
_eina_debug_chunk_tmp_reset(void)
{
   chunk3_num = 0;
}
# else
// implement with static buffers - once we exceed these we will fail. sorry
// maybe one day find another solution, but these buffers should be enough
// for now for thos eplatforms (like windows) where we can't do the mmap
// tricks above.
static unsigned char chunk1[8 * 1024];
static unsigned char chunk2[4 * 1024];
static unsigned char chunk3[128 * 1024];
static int chunk1_size = sizeof(chunk1);
static int chunk1_num = 0;
static int chunk2_size = sizeof(chunk2);
static int chunk2_num = 0;
static int chunk3_size = sizeof(chunk3);
static int chunk3_num = 0;

// push a new bit of mem on our growing stack of mem - given our workload,
// we never free anything here, only ever grow new things on this stack
void *
_eina_debug_chunk_push(int size)
{
   void *ptr;

   // round size up to the nearest pointer size for alignment
   size = sizeof(void *) * ((size + sizeof(void *) - 1) / sizeof(void *));
   // if we ran out of space - fail
   if ((chunk1_num + size) > chunk1_size) return NULL;
   // get the mem at the top of this stack and return it, then move along
   ptr = chunk1 + chunk1_num;
   chunk1_num += size;
   return ptr;
}

// grow a single existing chunk (we use this for the filename -> path lookup)
void *
_eina_debug_chunk_realloc(int size)
{
   // if we ran out of space - fail
   if (size > chunk2_size) return NULL;
   // record new size and return chunk ptr as we just re-use it
   chunk2_num = size;
   return chunk2;
}

// grow a single existing chunk (we use this for the filename -> path lookup)
void *
_eina_debug_chunk_tmp_push(int size)
{
   void *ptr;

   // round size up to the nearest pointer size for alignment
   size = sizeof(void *) * ((size + sizeof(void *) - 1) / sizeof(void *));
   // if we ran out of space - fail
   if ((chunk3_num + size) > chunk3_size) return NULL;
   // get the mem at the top of this stack and return it, then move along
   ptr = chunk3 + chunk1_num;
   chunk3_num += size;
   return ptr;
}

void
_eina_debug_chunk_tmp_reset(void)
{
   chunk3_num = 0;
}
# endif

// handy - duplicate a string on our growing stack - never expect to free it
char *
_eina_debug_chunk_strdup(const char *str)
{
   int len = strlen(str);
   char *s = _eina_debug_chunk_push(len + 1);
   if (!s) return NULL;
   strcpy(s, str);
   return s;
}
