// Copyright (C) 2004  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_MEMORY_MANAGER_KERNEl_3_
#define DLIB_MEMORY_MANAGER_KERNEl_3_
#include "../algs.h"
#include "memory_manager_kernel_abstract.h"
#include "../assert.h"
#include <new>
#include "memory_manager_kernel_2.h"
#include "../binary_search_tree/binary_search_tree_kernel_2.h"
namespace dlib
{
    template <
        typename T,
        size_t chunk_size
        >
    class memory_manager_kernel_3
    {
        /*!            
            INITIAL VALUE
                allocations == 0
                next == 0
                first_chunk == 0
                bst_of_arrays == 0
            REQUIREMENTS ON chunk_size
                chunk_size is the number of items of type T we will allocate at a time. so
                it must be > 0.
            CONVENTION
                This memory manager implementation allocates memory in blocks of chunk_size*sizeof(T)
                bytes.  All the sizeof(T) subblocks are kept in a linked list of free memory blocks
                and are given out whenever an allocation request occurs.  Also, memory is not freed
                until this object is destructed.  
                
                allocations == get_number_of_allocations()
                - if (next != 0) then
                    - next == the next pointer to return from allocate()
                      and next == pointer to the first node in a linked list.  each node
                      is one item in the memory pool.    
                    - the last node in the linked list has next set to 0
                - else
                    - we need to call new to get the next pointer to return from allocate()
                - if (arrays != 0) then
                    - someone has called allocate_array()
                    - (*arrays)[size] == an array of size bytes of memory  
                - if (first_chunk != 0) then
                    - first_chunk == the first node in a linked list that contains pointers 
                      to all the chunks we have ever allocated.  The last link in the list
                      has its next pointer set to 0.
        !*/
        union node
        {
            node* next;
            char item[sizeof(T)];
        };
        struct chunk_node
        {
            node* chunk;
            chunk_node* next;
        };
        typedef binary_search_tree_kernel_2<
            size_t,
            char*,
            memory_manager_kernel_2<char,5>
            > bst_of_arrays; 
    public:
        typedef T type;
        template <typename U>
        struct rebind {
            typedef memory_manager_kernel_3<U,chunk_size> other;
        };
        memory_manager_kernel_3(
        ) :
            allocations(0),
            next(0),
            first_chunk(0),
            arrays(0)
        {
            // You FOOL!  You can't have a zero chunk_size.
            COMPILE_TIME_ASSERT(chunk_size > 0);
        }
        virtual ~memory_manager_kernel_3(
        )
        {
            if (allocations == 0)
            {
                while (first_chunk != 0)
                {
                    chunk_node* temp = first_chunk;
                    first_chunk = first_chunk->next;
                    // delete the memory chunk 
                    ::operator delete ( static_cast<void*>(temp->chunk));
                    // delete the chunk_node
                    delete temp;
                }
            }
            if (arrays)
            {
                arrays->reset();
                while (arrays->move_next())
                {
                    ::operator delete (arrays->element().value());
                }
                delete arrays;
            }
        }
        size_t get_number_of_allocations (
        ) const { return allocations; }
        T* allocate_array (
            size_t size
        )
        {
            size_t block_size = sizeof(T)*size + sizeof(size_t)*2;
            // make sure we have initialized the arrays object.
            if (arrays == 0)
            {
                arrays = new bst_of_arrays;
            }
            char* temp;
            // see if we have a suitable block of memory already.
            arrays->position_enumerator(block_size);
            if (arrays->current_element_valid())
            {
                // we have a suitable block of memory already so use that one.
                arrays->remove_current_element(block_size,temp); 
            }
            else
            {
                temp = static_cast<char*>(::operator new(block_size));
            }
            reinterpret_cast<size_t*>(temp)[0] = block_size;
            reinterpret_cast<size_t*>(temp)[1] = size;
            temp += sizeof(size_t)*2;
            try
            {
                initialize_array(reinterpret_cast<T*>(temp),size);
            }
            catch (...)
            {
                // something was thrown while we were initializing the array so
                // stick our memory block into arrays and rethrow the exception
                temp -= sizeof(size_t)*2;
                arrays->add(block_size,temp);
                throw;
            }
            ++allocations;
            return reinterpret_cast<T*>(temp);
        }
        void deallocate_array (
            T* item
        )
        {
            char* temp = reinterpret_cast<char*>(item);
            temp -= sizeof(size_t)*2;
            size_t block_size = reinterpret_cast<size_t*>(temp)[0];
            size_t size = reinterpret_cast<size_t*>(temp)[1];
            deinitialize_array(item,size);
            arrays->add(block_size,temp);
            
            --allocations;
        }
        T* allocate (
        ) 
        {              
            T* temp;
            if (next != 0)
            {
                temp = reinterpret_cast<T*>(next);
                node* n = next->next;
                try
                {
                    // construct this new T object with placement new.
                    new (static_cast<void*>(temp))T();
                }
                catch (...)
                {
                    next->next = n;
                    throw;
                }
                next = n;
            }
            else
            {
                // the linked list is empty so we need to allocate some more memory
                node* block = static_cast<node*>(::operator new (sizeof(node)*chunk_size));
                // the first part of this block can be our new object
                temp = reinterpret_cast<T*>(block);
                try
                {
                    // construct this new T object with placement new.
                    new (static_cast<void*>(temp))T();
                }
                catch (...)
                {
                    // construction of the new object threw so delete the block of memory
                    ::operator delete ( static_cast<void*>(block));
                    throw;
                }
                // allocate a new chunk_node
                chunk_node* chunk;
                try {chunk = new chunk_node; }
                catch (...) 
                { 
                    temp->~T();
                    ::operator delete ( static_cast<void*>(block));
                    throw;
                }
                // add this block into the chunk list
                chunk->chunk = block;
                chunk->next = first_chunk;
                first_chunk = chunk;
                ++block;
                // now add the rest of the block into the linked list of free nodes.
                for (size_t i = 0; i < chunk_size-1; ++i)
                {
                    block->next = next;
                    next = block;
                    ++block;
                }
            }
            ++allocations;
            return temp;
        }
        void deallocate (
            T* item
        ) 
        { 
            --allocations;  
            item->~T();
            // add this memory into our linked list.
            node* temp = reinterpret_cast<node*>(item);
            temp->next = next;
            next = temp;                
        }
        void swap (
            memory_manager_kernel_3& item
        ) 
        { 
            exchange(allocations,item.allocations); 
            exchange(next,item.next); 
            exchange(first_chunk,item.first_chunk);
            exchange(arrays,item.arrays);
        }
    private:
        // data members
        size_t allocations;
        node* next;
        chunk_node* first_chunk;
        bst_of_arrays* arrays;
        void initialize_array (
            T* array,
            size_t size
        ) const
        {
            size_t i;
            try
            {
                for (i = 0; i < size; ++i)
                {
                    // construct this new T object with placement new.
                    new (static_cast<void*>(array+i))T();
                }
            }
            catch (...)
            {
                // Catch any exceptions thrown during the construction process
                // and then destruct any T objects that actually were successfully
                // constructed.
                for (size_t j = 0; j < i; ++j)
                {
                    array[i].~T();
                }
                throw;
            }
        }
        void deinitialize_array (
            T* array,
            size_t size
        ) const
        {
            for (size_t i = 0; i < size; ++i)
            {
                array[i].~T();
            }
        }
        // don't do any initialization for the built in types
        void initialize_array(unsigned char*, size_t) {} 
        void deinitialize_array(unsigned char*, size_t) {}
        void initialize_array(signed char*, size_t) {} 
        void deinitialize_array(signed char*, size_t) {}
        void initialize_array(char*, size_t) {} 
        void deinitialize_array(char*, size_t) {}
        void initialize_array(int*, size_t) {} 
        void deinitialize_array(int*, size_t) {}
        void initialize_array(unsigned int*, size_t) {} 
        void deinitialize_array(unsigned int*, size_t) {}
        void initialize_array(unsigned long*, size_t) {} 
        void deinitialize_array(unsigned long*, size_t) {}
        void initialize_array(long*, size_t) {} 
        void deinitialize_array(long*, size_t) {}
        void initialize_array(float*, size_t) {} 
        void deinitialize_array(float*, size_t) {}
        void initialize_array(double*, size_t) {} 
        void deinitialize_array(double*, size_t) {}
        void initialize_array(short*, size_t) {} 
        void deinitialize_array(short*, size_t) {}
        void initialize_array(unsigned short*, size_t) {} 
        void deinitialize_array(unsigned short*, size_t) {}
        // restricted functions
        memory_manager_kernel_3(memory_manager_kernel_3&);        // copy constructor
        memory_manager_kernel_3& operator=(memory_manager_kernel_3&);    // assignment operator
    };
    template <
        typename T,
        size_t chunk_size
        >
    inline void swap (
        memory_manager_kernel_3<T,chunk_size>& a, 
        memory_manager_kernel_3<T,chunk_size>& b 
    ) { a.swap(b); }   
// ----------------------------------------------------------------------------------------
}
#endif // DLIB_MEMORY_MANAGER_KERNEl_3_