/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2018-2020, Danny Robson <danny@nerdcruft.net>
 */

#pragma once


#include "traits.hpp"

#include "../buffer/traits.hpp"
#include "../concepts.hpp"
#include "../std.hpp"
#include "../view.hpp"

#include <utility>
#include <memory>


namespace cruft::alloc::easy {
    /// Provides an interface suitable for allocating and constructing typed
    /// objects using a reference to an existing allocator.
    template <typename AllocatorT>
    class passthrough {
    public:
        explicit passthrough (AllocatorT &_allocator)
            : m_allocator (_allocator)
        { ; }


        /// Allocate, construct, and return a type `U` using memory from the
        /// underlying allocator with the default alignment.
        template <typename U, typename ...Args>
        U*
        acquire (Args&&...args)&
        {
            auto memory = m_allocator.allocate (sizeof (U), alignof (U));
            try {
                return new (memory.data ()) U (std::forward<Args> (args)...);
            } catch (...) {
                m_allocator.deallocate (memory.data (), sizeof (U), alignof (U));
                throw;
            }
        }


        /// Destruct and deallocate an object which has previously been
        /// allocated through this interface.
        template <typename U>
        void release (U *ptr)
        {
            ptr->~U ();
            m_allocator.deallocate (ptr, sizeof (U), alignof (U));
        }


        /// Create an object of type `U` using `acquire` and wrap the result
        /// in a std::unique_ptr that will call release on this interface at d
        /// struction time.
        template <typename U, typename ...Args>
        auto unique (Args &&...args)&
        {
            struct deleter {
                passthrough &m_owner;

                void operator() (U *ptr)
                { return m_owner.release (ptr); }
            };

            return std::unique_ptr (
                acquire (std::forward<Args> (args)...),
                deleter {*this}
            );
        }


        template <typename T>
        decltype (auto)
        offset (T const *ptr)
        {
            return m_allocator.offset (ptr);
        }


        decltype(auto) data  (void) { return m_allocator.data  (); }
        decltype(auto) begin (void) { return m_allocator.begin (); }
        decltype(auto) end   (void) { return m_allocator.end   (); }

        decltype(auto) remain (void) { return m_allocator.remain (); }

        decltype(auto) offset (void const *ptr) { return m_allocator.offset (ptr); }


    private:
        AllocatorT &m_allocator;
    };


    /// An interfaces that manages an allocator and a backing store, and
    /// provides a simple interface for constructing arbitrary objects using
    /// the child objects.
    template <
        typename AllocatorT,
        typename = std::enable_if_t<is_allocator_v<AllocatorT>>
    >
    class backed {
    public:
        template <typename BufferT, typename ...Args>
        explicit backed (BufferT &_buffer, Args&&...args)
            : m_allocator (_buffer, std::forward<Args> (args)...)
        { ; }


        /// Allocate, construct, and return a type `U` using memory from the
        /// underlying allocator with the default alignment.
        template <typename U, typename ...Args>
        U*
        acquire (Args&&...args)&
        {
            auto memory = m_allocator.allocate (sizeof (U), alignof (U));
            try {
                return new (memory.data ()) U (std::forward<Args> (args)...);
            } catch (...) {
                m_allocator.deallocate (memory.data (), memory.size (), alignof (U));
                throw;
            }
        }


        /// Destruct and deallocate an object which has previously been
        /// allocated through this interface.
        template <typename U>
        void release (U *ptr)
        {
            ptr->~U ();
            m_allocator.deallocate (
                reinterpret_cast<u08*> (ptr),
                sizeof (U),
                alignof (U)
            );
        }



        template <typename T>
        cruft::view<T*>
        array (std::size_t count)
        {
            auto mem = m_allocator.allocate (count * sizeof (T), alignof (T)).template cast<T*> ();
            try {
                new (mem.data ()) T[count];
                return mem;
            } catch (...) {
                m_allocator.deallocate (mem.data (), count * sizeof (T), alignof (T));
                throw;
            }
        }


        /// Allocates storage for an array of ValueT, moves the values from
        /// ContainerT into this storage, and returns a view over the memory.
        template <
            concepts::iterable ContainerT
        >
        auto
        array (ContainerT &&container)
        {
            using IteratorT = decltype (std::begin (container));
            using ValueT = typename std::iterator_traits<IteratorT>::value_type;

            auto store = array<ValueT> (std::size (container));
            std::copy (
                std::move_iterator (std::begin (container)),
                std::move_iterator (std::end   (container)),
                std::begin (store)
            );

            return store;
        }


        /// Create an object of type `U` using `acquire` and wrap the result
        /// in a std::unique_ptr that will call release on this interface at d
        /// struction time.
        template <typename U, typename ...Args>
        auto unique (Args &&...args)&
        {
            struct deleter {
                backed &m_owner;

                void operator() (U *ptr)
                { return m_owner.release (ptr); }
            };

            return std::unique_ptr<U,deleter> (
                acquire<U> (std::forward<Args> (args)...),
                deleter {*this}
            );
        }


        decltype(auto) data  (void) { return m_allocator.data  (); }
        decltype(auto) begin (void) { return m_allocator.begin (); }
        decltype(auto) end   (void) { return m_allocator.end   (); }

        decltype(auto) used (void) { return m_allocator.used (); }
        decltype(auto) remain (void) { return m_allocator.remain (); }

        decltype(auto) offset (void const *ptr) { return m_allocator.offset (ptr); }

        template <typename T>
        decltype(auto)
        offset (cruft::view<T*> ptr)
        {
            return offset (ptr.data ());
        }


    private:
        AllocatorT m_allocator;
    };


    /// An interfaces that manages an allocator and a backing store, and
    /// provides a simple interface for constructing arbitrary objects using
    /// the child objects.
    template <
        typename AllocatorT,
        typename BufferT,
        typename = std::enable_if_t<true
            //&& memory::buffer::is_buffer_v<BufferT>
            && is_allocator_v<AllocatorT>
        >
    >
    class owned {
    public:
        explicit owned (BufferT &&_buffer)
            : m_buffer (std::move (_buffer))
            , m_allocator (m_buffer)
        { ; }


        /// Allocate, construct, and return a type `U` using memory from the
        /// underlying allocator with the default alignment.
        template <typename U, typename ...Args>
        U*
        acquire (Args&&...args)&
        {
            auto memory = m_allocator.allocate (sizeof (U), alignof (U));
            try {
                return new (memory.data ()) U (std::forward<Args> (args)...);
            } catch (...) {
                m_allocator.deallocate (memory.data (), memory.size (), alignof (U));
                throw;
            }
        }


        /// Destruct and deallocate an object which has previously been
        /// allocated through this interface.
        template <typename U>
        void release (U *ptr)
        {
            ptr->~U ();
            m_allocator.deallocate (
                reinterpret_cast<u08*> (ptr),
                sizeof (U),
                alignof (U)
            );
        }


        /// Create an object of type `U` using `acquire` and wrap the result
        /// in a std::unique_ptr that will call release on this interface at d
        /// struction time.
        template <typename U, typename ...Args>
        auto unique (Args &&...args)&
        {
            struct deleter {
                owned &m_owner;

                void operator() (U *ptr)
                { return m_owner.release (ptr); }
            };

            return std::unique_ptr<U,deleter> (
                acquire<U> (std::forward<Args> (args)...),
                deleter {*this}
            );
        }


    private:
        BufferT m_buffer;
        AllocatorT m_allocator;
    };
};