// Copyright (C) 2007  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_STD_ALLOc_H_
#define DLIB_STD_ALLOc_H_

#include <limits>
#include <memory>
#include "enable_if.h"
#include "algs.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------

    template <
        typename T,
        typename M
        >
    class std_allocator 
    {
        /*!
            REQUIREMENTS ON M 
                must be an implementation of memory_manager/memory_manager_kernel_abstract.h or
                must be an implementation of memory_manager_global/memory_manager_global_kernel_abstract.h or
                must be an implementation of memory_manager_stateless/memory_manager_stateless_kernel_abstract.h 
                M::type can be set to anything.

            WHAT THIS OBJECT REPRESENTS
                This object is an implementation of an allocator that conforms to the C++ standard 
                requirements for allocator objects.  The M template argument is one of the dlib
                memory manager objects and this allocator implementation will do all of its memory allocations
                using whatever dlib memory manager you supply.   

                Thus, using this allocator object you can use any of the dlib memory manager objects with
                the containers in the STL or with any other object that requires a C++ allocator object.

                It is important to note that many STL implementations make the assumption that the memory
                allocated by one allocator can be freed by another.  This effectively means that you should
                only use a global or stateless memory manager with the std_allocator.  Either that or you
                have to verify that your version of the STL isn't going to try and allocate and deallocate
                memory with different allocators.
        !*/

    public:
        //type definitions
        typedef std::size_t     size_type;
        typedef std::ptrdiff_t  difference_type;
        typedef T*              pointer;
        typedef const T*        const_pointer;
        typedef T&              reference;
        typedef const T&        const_reference;
        typedef T               value_type;

        //rebind std_allocator to type U
        template <typename U>
        struct rebind {
            typedef std_allocator<U,M> other;
        };

        //return address of values
        pointer address (reference value) const { return &value; }

        const_pointer address (const_reference value) const { return &value; }

        /*constructors and destructor
         *-nothing to do because the std_allocator has no state
        */
        std_allocator() throw() { }

        std_allocator(const std_allocator&) throw() { } 

        template <typename U>
        std_allocator (const std_allocator<U,M>&) throw() { }

        ~std_allocator() throw() { }

        //return maximum number of elements that can be allocated
        size_type max_size () const throw() 
        {
            //for numeric_limits see Section 4.3, page 59
            return std::numeric_limits<size_t>::max() / sizeof(T);
        }

        //allocate but don't initialize num elements of type T
        pointer allocate (
            size_type num,
            typename std_allocator<void,M>::const_pointer  = 0
        ) 
        {
            return (pointer) pool.allocate_array(num*sizeof(T));
        }

        // This function is not required by the C++ standard but some versions of the STL
        // distributed with gcc erroneously require it.  See the bug report for further
        // details: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51626
        void construct(pointer p) { return construct(p, value_type()); }

        //initialize elements of allocated storage p with value value
        void construct (pointer p, const T& value) 
        {
            //initialize memory with placement new
            new((void*)p)T(value);
        }


        //destroy elements of initialized storage p
        void destroy (pointer p) 
        {
            // destroy objects by calling their destructor
            p->~T();
        }

        //deallocate storage p of deleted elements
        void deallocate (pointer p, size_type ) 
        {
            pool.deallocate_array((char*)p);
        }

        void swap (
            std_allocator& item
        )
        {
            pool.swap(item.pool);
        }

        std_allocator& operator= (const std_allocator&) { return *this;}

    private:
        typename M::template rebind<char>::other pool; 
    };

// ----------------------------------------------------------------------------------------

    template <
        typename M
        >
    class std_allocator<void,M> 
    {
    public:
        //type definitions
        typedef std::size_t     size_type;
        typedef std::ptrdiff_t  difference_type;
        typedef void*              pointer;
        typedef const void*        const_pointer;
        typedef void               value_type;

        //rebind std_allocator to type U
        template <typename U>
        struct rebind {
            typedef std_allocator<U,M> other;
        };

    };
    
// ----------------------------------------------------------------------------------------

    template <typename M1, typename M2, typename enabled = void>
    struct std_alloc_compare
    { const static bool are_interchangeable = false; };

    template <typename M1, typename M2>
    struct std_alloc_compare<M1,M2,typename enable_if<is_same_type<typename M1::mm_global_type, typename M2::mm_global_type> >::type>
    { const static bool are_interchangeable = true; };

    template <typename M>
	struct std_alloc_compare<M,M,typename enable_if_c<M::is_stateless>::type>
    { const static bool are_interchangeable = true; };

    //return that all specializations of this std_allocator are interchangeable if they use memory_manager_global
    // instances with the same mm_global_type
    template <typename T1, typename M1, typename T2, typename M2>
    bool operator== (
        const std_allocator<T1,M1>&,
        const std_allocator<T2,M2>&
    ) throw() 
    { return std_alloc_compare<M1,M2>::are_interchangeable; }

    template <typename T1, typename M1, typename T2, typename M2>
    bool operator!= (
        const std_allocator<T1,M1>&,
        const std_allocator<T2,M2>&
    ) throw() 
    { return !std_alloc_compare<M1,M2>::are_interchangeable; }

// ----------------------------------------------------------------------------------------

    template <typename T, typename M>
    void swap (
        std_allocator<T,M>& a,
        std_allocator<T,M>& b
    ) { a.swap(b); }

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_STD_ALLOc_H_