// This is core/vnl/vnl_alloc.cxx

#include <cstring>
#include <cstdlib>
#include <iostream>
#include "vnl_alloc.h"

char *
vnl_alloc::chunk_alloc(std::size_t size, int & nobjs)
{
  char * result = nullptr;
  std::size_t total_bytes = size * nobjs;
  const std::size_t bytes_left = end_free - start_free;

  if (bytes_left >= total_bytes)
  {
    result = start_free;
    start_free += total_bytes;
    return result;
  }
  else if (bytes_left >= size)
  {
    nobjs = int(bytes_left / size);
    total_bytes = size * nobjs;
    result = start_free;
    start_free += total_bytes;
    return result;
  }
  else
  {
    const std::size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
    // Try to make use of the left-over piece.
    if (bytes_left > 0)
    {
      obj ** const my_free_list = free_list + FREELIST_INDEX(bytes_left);
      ((obj *)start_free)->free_list_link = *my_free_list;
      *my_free_list = (obj *)start_free;
    }
    start_free = (char *)std::malloc(bytes_to_get);
    if (nullptr == start_free)
    {
      // Try to make do with what we have.  That can't
      // hurt.  We do not try smaller requests, since that tends
      // to result in disaster on multi-process machines.
      for (std::size_t i = size; i <= VNL_ALLOC_MAX_BYTES; i += VNL_ALLOC_ALIGN)
      {
        obj ** const my_free_list = free_list + FREELIST_INDEX(i);
        obj * const p = *my_free_list;
        if (nullptr != p)
        {
          *my_free_list = p->free_list_link;
          start_free = (char *)p;
          end_free = start_free + i;
          return chunk_alloc(size, nobjs);
          // Any leftover piece will eventually make it to the
          // right free std::list.
        }
      }
      start_free = (char *)std::malloc(bytes_to_get);
      // This should either throw an
      // exception or remedy the situation.  Thus we assume it
      // succeeded.
    }
    heap_size += bytes_to_get;
    end_free = start_free + bytes_to_get;
    return chunk_alloc(size, nobjs);
  }
}


/* Returns an object of size n, and optionally adds to size n free std::list.*/
/* We assume that n is properly aligned.                                */
/* We hold the allocation lock.                                         */
void *
vnl_alloc::refill(std::size_t n)
{
  int nobjs = 20;
  char * const chunk = chunk_alloc(n, nobjs);
  obj * next_obj = nullptr;

  if (1 == nobjs)
    return chunk;
  obj ** const my_free_list = free_list + FREELIST_INDEX(n);

  /* Build free std::list in chunk */
  obj * const result = (obj *)chunk;
  *my_free_list = next_obj = (obj *)(chunk + n);
  for (int i = 1;; i++)
  {
    obj * const current_obj = next_obj;
    next_obj = (obj *)((char *)next_obj + n);
    if (nobjs - 1 == i)
    {
      current_obj->free_list_link = nullptr;
      break;
    }
    else
    {
      current_obj->free_list_link = next_obj;
    }
  }
  return result;
}

void *
vnl_alloc::reallocate(void * p, std::size_t old_sz, std::size_t new_sz)
{

  if (old_sz > VNL_ALLOC_MAX_BYTES && new_sz > VNL_ALLOC_MAX_BYTES)
  {
    return std::realloc(p, new_sz);
  }
  if (ROUND_UP(old_sz) == ROUND_UP(new_sz))
    return p;
  void * const result = allocate(new_sz);
  const std::size_t copy_sz = new_sz > old_sz ? old_sz : new_sz;
  std::memcpy(result, p, copy_sz);
  deallocate(p, old_sz);
  return result;
}

char * vnl_alloc::start_free = nullptr;
char * vnl_alloc::end_free = nullptr;
std::size_t vnl_alloc::heap_size = 0;

vnl_alloc::obj * vnl_alloc::free_list[VNL_ALLOC_NFREELISTS] = { nullptr };

#ifdef TEST
int
main()
{
  char * p = (char *)vnl_alloc::allocate(10);
  std::strcpy(p, "fred\n");
  std::cerr << p << '\n';
  vnl_alloc::deallocate(p, 10);
}

#endif // TEST
