// File: crn_mem.h
// See Copyright Notice and license at the end of inc/crnlib.h
#pragma once

#ifndef CRNLIB_MIN_ALLOC_ALIGNMENT
#define CRNLIB_MIN_ALLOC_ALIGNMENT sizeof(size_t) * 2
#endif

namespace crnlib
{
#if CRNLIB_64BIT_POINTERS
   const uint64 CRNLIB_MAX_POSSIBLE_BLOCK_SIZE = 0x400000000ULL;
#else
   const uint32 CRNLIB_MAX_POSSIBLE_BLOCK_SIZE = 0x7FFF0000U;
#endif

   void*    crnlib_malloc(size_t size);
   void*    crnlib_malloc(size_t size, size_t* pActual_size);
   void*    crnlib_realloc(void* p, size_t size, size_t* pActual_size = NULL, bool movable = true);
   void*    crnlib_calloc(size_t count, size_t size, size_t* pActual_size = NULL);
   void     crnlib_free(void* p);
   size_t   crnlib_msize(void* p);
   void     crnlib_print_mem_stats();
   void     crnlib_mem_error(const char* p_msg);
   
   // omfg - there must be a better way
   
   template<typename T>
   inline T* crnlib_new()
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      if (CRNLIB_IS_SCALAR_TYPE(T))
         return p;
      return helpers::construct(p);
   }

   template<typename T, typename A>
   inline T* crnlib_new(const A& init0)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0); 
   }

   template<typename T, typename A>
   inline T* crnlib_new(A& init0)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0); 
   }

   template<typename T, typename A, typename B>
   inline T* crnlib_new(const A& init0, const B& init1)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1); 
   }

   template<typename T, typename A, typename B, typename C>
   inline T* crnlib_new(const A& init0, const B& init1, const C& init2)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1, init2); 
   }

   template<typename T, typename A, typename B, typename C, typename D>
   inline T* crnlib_new(const A& init0, const B& init1, const C& init2, const D& init3)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1, init2, init3); 
   }

   template<typename T, typename A, typename B, typename C, typename D, typename E>
   inline T* crnlib_new(const A& init0, const B& init1, const C& init2, const D& init3, const E& init4)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1, init2, init3, init4); 
   }

   template<typename T, typename A, typename B, typename C, typename D, typename E, typename F>
   inline T* crnlib_new(const A& init0, const B& init1, const C& init2, const D& init3, const E& init4, const F& init5)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1, init2, init3, init4, init5); 
   }

   template<typename T, typename A, typename B, typename C, typename D, typename E, typename F, typename G>
   inline T* crnlib_new(const A& init0, const B& init1, const C& init2, const D& init3, const E& init4, const F& init5, const G& init6)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1, init2, init3, init4, init5, init6); 
   }

   template<typename T, typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H>
   inline T* crnlib_new(const A& init0, const B& init1, const C& init2, const D& init3, const E& init4, const F& init5, const G& init6, const H& init7)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1, init2, init3, init4, init5, init6, init7); 
   }

   template<typename T, typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I>
   inline T* crnlib_new(const A& init0, const B& init1, const C& init2, const D& init3, const E& init4, const F& init5, const G& init6, const H& init7, const I& init8)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1, init2, init3, init4, init5, init6, init7, init8); 
   }

   template<typename T, typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J>
   inline T* crnlib_new(const A& init0, const B& init1, const C& init2, const D& init3, const E& init4, const F& init5, const G& init6, const H& init7, const I& init8, const J& init9)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1, init2, init3, init4, init5, init6, init7, init8, init9); 
   }

   template<typename T, typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K>
   inline T* crnlib_new(const A& init0, const B& init1, const C& init2, const D& init3, const E& init4, const F& init5, const G& init6, const H& init7, const I& init8, const J& init9, const K& init10)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1, init2, init3, init4, init5, init6, init7, init8, init9, init10); 
   }

   template<typename T, typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L>
   inline T* crnlib_new(const A& init0, const B& init1, const C& init2, const D& init3, const E& init4, const F& init5, const G& init6, const H& init7, const I& init8, const J& init9, const K& init10, const L& init11)
   {
      T* p = static_cast<T*>(crnlib_malloc(sizeof(T)));
      return new (static_cast<void*>(p)) T(init0, init1, init2, init3, init4, init5, init6, init7, init8, init9, init10, init11); 
   }

   template<typename T>
   inline T* crnlib_new_array(uint32 num)
   {
      if (!num) num = 1;

      uint64 total = CRNLIB_MIN_ALLOC_ALIGNMENT + sizeof(T) * num;
      if (total > CRNLIB_MAX_POSSIBLE_BLOCK_SIZE)
      {
         crnlib_mem_error("crnlib_new_array: Array too large!");
         return NULL;
      }
      uint8* q = static_cast<uint8*>(crnlib_malloc(static_cast<size_t>(total)));

      T* p = reinterpret_cast<T*>(q + CRNLIB_MIN_ALLOC_ALIGNMENT);

      reinterpret_cast<uint32*>(p)[-1] = num;
      reinterpret_cast<uint32*>(p)[-2] = ~num;

      if (!CRNLIB_IS_SCALAR_TYPE(T))
      {
         helpers::construct_array(p, num);
      }
      return p;
   }

   template<typename T> 
   inline void crnlib_delete(T* p)
   {
      if (p) 
      {
         if (!CRNLIB_IS_SCALAR_TYPE(T))
         {
            helpers::destruct(p);
         }
         crnlib_free(p);
      }         
   }

   template<typename T> 
   inline void crnlib_delete_array(T* p)
   {
      if (p)
      {
         const uint32 num = reinterpret_cast<uint32*>(p)[-1];
         const uint32 num_check = reinterpret_cast<uint32*>(p)[-2];
         CRNLIB_ASSERT(num && (num == ~num_check));
         if (num == ~num_check)
         {
            if (!CRNLIB_IS_SCALAR_TYPE(T))
            {
               helpers::destruct_array(p, num);
            }

            crnlib_free(reinterpret_cast<uint8*>(p) - CRNLIB_MIN_ALLOC_ALIGNMENT);
         }
      }
   }   
   
} // namespace crnlib
#define CRNLIB_DEFINE_NEW_DELETE \
   void* operator new (size_t size) \
   { \
      void* p = crnlib::crnlib_malloc(size); \
      if (!p) \
         crnlib_fail("new: Out of memory!", __FILE__, __LINE__); \
      return p; \
   } \
   void* operator new[] (size_t size) \
   { \
      void* p = crnlib::crnlib_malloc(size); \
      if (!p) \
         crnlib_fail("new[]: Out of memory!", __FILE__, __LINE__); \
      return p; \
   } \
   void operator delete (void* p_block) \
   { \
      crnlib::crnlib_free(p_block); \
   } \
   void operator delete[] (void* p_block) \
   { \
      crnlib::crnlib_free(p_block); \
   }