/*
 * Copyright (c) 2012-2014 Glen Joseph Fernandes 
 * glenfe at live dot com
 *
 * Distributed under the Boost Software License, 
 * Version 1.0. (See accompanying file LICENSE_1_0.txt 
 * or copy at http://boost.org/LICENSE_1_0.txt)
 */
#ifndef BOOST_SMART_PTR_DETAIL_ARRAY_ALLOCATOR_HPP
#define BOOST_SMART_PTR_DETAIL_ARRAY_ALLOCATOR_HPP

#include <boost/align/align.hpp>
#include <boost/smart_ptr/detail/array_traits.hpp>
#include <boost/smart_ptr/detail/array_utility.hpp>
#include <boost/type_traits/alignment_of.hpp>

namespace boost {
    namespace detail {
        struct ms_init_tag   { };
        struct ms_noinit_tag { };

        template<class T>
        struct ms_allocator_state;

        template<class T>
        struct ms_allocator_state<T[]> {
            typedef typename array_base<T>::type type;

            ms_allocator_state(std::size_t size_,
                type** result_)
                : size(size_ * array_total<T>::size),
                  result(result_) {
            }

            std::size_t size;

            union {
                type** result;
                type* object;
            };
        };

        template<class T, std::size_t N>
        struct ms_allocator_state<T[N]> {
            typedef typename array_base<T>::type type;

            ms_allocator_state(type** result_)
                : result(result_) {
            }

            enum {
                size = array_total<T[N]>::size
            };

            union {
                type** result;
                type* object;
            };
        };

        template<class A, class T, class R>
        class as_allocator
            : public A {
            template<class A_, class T_, class R_>
            friend class as_allocator;

#if !defined(BOOST_NO_CXX11_ALLOCATOR)
            typedef std::allocator_traits<A> AT;
            typedef typename AT::template rebind_alloc<char> CA;
            typedef typename AT::template rebind_traits<char> CT;
#else
            typedef typename A::template rebind<char>::other CA;
#endif

        public:
            typedef A allocator_type;

#if !defined(BOOST_NO_CXX11_ALLOCATOR)
            typedef typename AT::value_type value_type;
            typedef typename AT::pointer pointer;
            typedef typename AT::const_pointer const_pointer;
            typedef typename AT::void_pointer void_pointer;
            typedef typename AT::const_void_pointer const_void_pointer;
            typedef typename AT::size_type size_type;
            typedef typename AT::difference_type difference_type;
#else
            typedef typename A::value_type value_type;
            typedef typename A::pointer pointer;
            typedef typename A::const_pointer const_pointer;
            typedef typename A::size_type size_type;
            typedef typename A::difference_type difference_type;
            typedef typename A::reference reference;
            typedef typename A::const_reference const_reference;
            typedef void* void_pointer;
            typedef const void* const_void_pointer;
#endif

            template<class U>
            struct rebind {
#if !defined(BOOST_NO_CXX11_ALLOCATOR)
                typedef as_allocator<typename AT::
                    template rebind_alloc<U>, T, R> other;
#else
                typedef as_allocator<typename A::
                    template rebind<U>::other, T, R> other;
#endif
            };

            typedef typename array_base<T>::type type;

            as_allocator(const A& allocator_, type** result)
                : A(allocator_),
                  data(result) {
            }

            as_allocator(const A& allocator_, std::size_t size,
                type** result)
                : A(allocator_),
                  data(size, result) {
            }

            template<class U>
            as_allocator(const as_allocator<U, T, R>& other)
                : A(other.allocator()),
                  data(other.data) {
            }

            pointer allocate(size_type count, const_void_pointer = 0) {
                enum {
                    M = boost::alignment_of<type>::value
                };
                std::size_t n1 = count * sizeof(value_type);
                std::size_t n2 = data.size * sizeof(type);
                std::size_t n3 = n2 + M;
                CA ca(allocator());
                void* p1 = ca.allocate(n1 + n3);
                void* p2 = static_cast<char*>(p1) + n1;
                (void)boost::alignment::align(M, n2, p2, n3);
                *data.result = static_cast<type*>(p2);
                return static_cast<value_type*>(p1);
            }

            void deallocate(pointer memory, size_type count) {
                enum {
                    M = boost::alignment_of<type>::value
                };
                std::size_t n1 = count * sizeof(value_type);
                std::size_t n2 = data.size * sizeof(type) + M;
                char* p1 = reinterpret_cast<char*>(memory);
                CA ca(allocator());
                ca.deallocate(p1, n1 + n2);
            }

            const A& allocator() const {
                return static_cast<const A&>(*this);
            }

            A& allocator() {
                return static_cast<A&>(*this);
            }

            void set(type* memory) {
                data.object = memory;
            }

            void operator()() {
                if (data.object) {
                    R tag;
                    release(tag);
                }
            }

        private:
            void release(ms_init_tag) {
#if !defined(BOOST_NO_CXX11_ALLOCATOR)
                as_destroy(allocator(), data.object, data.size);
#else
                ms_destroy(data.object, data.size);
#endif
            }

            void release(ms_noinit_tag) {
                ms_destroy(data.object, data.size);
            }

            ms_allocator_state<T> data;
        };

        template<class A1, class A2, class T, class R>
        bool operator==(const as_allocator<A1, T, R>& a1,
            const as_allocator<A2, T, R>& a2) {
            return a1.allocator() == a2.allocator();
        }

        template<class A1, class A2, class T, class R>
        bool operator!=(const as_allocator<A1, T, R>& a1,
            const as_allocator<A2, T, R>& a2) {
            return a1.allocator() != a2.allocator();
        }

        template<class T, class Y = char>
        class ms_allocator;

        template<class T, class Y>
        class ms_allocator {
            template<class T_, class Y_>
            friend class ms_allocator;

        public:
            typedef typename array_base<T>::type type;

            typedef Y value_type;
            typedef Y* pointer;
            typedef const Y* const_pointer;
            typedef std::size_t size_type;
            typedef std::ptrdiff_t difference_type;
            typedef Y& reference;
            typedef const Y& const_reference;

            template<class U>
            struct rebind {
                typedef ms_allocator<T, U> other;
            };

            ms_allocator(type** result)
                : data(result) {
            }

            ms_allocator(std::size_t size, type** result)
                : data(size, result) {
            }

            template<class U>
            ms_allocator(const ms_allocator<T, U>& other)
                : data(other.data) {
            }

            pointer allocate(size_type count, const void* = 0) {
                enum {
                    M = boost::alignment_of<type>::value
                };
                std::size_t n1 = count * sizeof(Y);
                std::size_t n2 = data.size * sizeof(type);
                std::size_t n3 = n2 + M;
                void* p1 = ::operator new(n1 + n3);
                void* p2 = static_cast<char*>(p1) + n1;
                (void)boost::alignment::align(M, n2, p2, n3);
                *data.result = static_cast<type*>(p2);
                return static_cast<Y*>(p1);
            }

            void deallocate(pointer memory, size_type) {
                void* p1 = memory;
                ::operator delete(p1);
            }

#if defined(BOOST_NO_CXX11_ALLOCATOR)
            pointer address(reference value) const {
                return &value;
            }

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

            size_type max_size() const {
                enum {
                    N = static_cast<std::size_t>(-1) / sizeof(Y)
                };
                return N;
            }

            void construct(pointer memory, const_reference value) {
                void* p1 = memory;
                ::new(p1) Y(value);
            }

            void destroy(pointer memory) {
                (void)memory;
                memory->~Y();
            }
#endif

            void set(type* memory) {
                data.object = memory;
            }

            void operator()() {
                if (data.object) {
                    ms_destroy(data.object, data.size);
                }
            }

        private:
            ms_allocator_state<T> data;
        };

        template<class T, class Y1, class Y2>
        bool operator==(const ms_allocator<T, Y1>&,
            const ms_allocator<T, Y2>&) {
            return true;
        }

        template<class T, class Y1, class Y2>
        bool operator!=(const ms_allocator<T, Y1>&,
            const ms_allocator<T, Y2>&) {
            return false;
        }

        class ms_in_allocator_tag {
        public:
            void operator()(const void*) {
            }
        };
    }
}

#endif
