/*
 * 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 2015-2018 Danny Robson <danny@nerdcruft.net>
 */

#ifndef CRUFT_UTIL_ALLOC_ARENA_HPP
#define CRUFT_UTIL_ALLOC_ARENA_HPP

#include "../memory/deleter.hpp"
#include "../cast.hpp"
#include "../view.hpp"

#include <memory>
#include <utility>

namespace cruft::alloc {
    /// wraps a block allocator with an interface suitable for allocating
    /// individual objects.
    template <class T>
    class arena {
    public:
        explicit arena (T &store):
            m_store (store)
        { ; }

        //---------------------------------------------------------------------
        template <typename U, typename ...Args>
        U*
        acquire (Args&&... args)
        {
            U *data = m_store.template allocate<U> (1).data ();

            try {
                new (data) U (std::forward<Args> (args)...);
            } catch (...) {
                m_store.template deallocate<U> ({data,1});
                throw;
            }

            return data;
        }

        //---------------------------------------------------------------------
        template <typename U>
        void
        release (U *u)
        {
            u->~U ();
            m_store.template deallocate<U> (cruft::view {u,1u});
        }


        //---------------------------------------------------------------------
        template <typename U>
        using deleter_t = cruft::memory::owner_deleter<
            U,arena<T>,&arena::release
        >;

        template <typename U>
        using unique_t = std::unique_ptr<U,deleter_t<U>>;

        // the return type must be auto and the implementation must be inline
        // otherwise we trigger an internal compiler error in gcc-5.2.0
        // "sorry, unimplemented: mangling offset_ref"
        template <typename U, typename ...Args>
        auto
        unique (Args&& ...args)
        {
            return unique_t<U> {
                acquire<U> (std::forward<Args> (args)...),
                deleter_t<U> (*this)
            };
        }

    private:
        T &m_store;
    };


    /// A simple allocator that contains a raw allocator and a forwarded
    /// allocator.
    ///
    /// The raw allocator handles the memory allocation, the forwarded
    /// allocator performs the initialisation, and we control the construction
    /// of both.
    ///
    /// Ideally we wouldn't forward calls manually and instead do something
    /// like inherit from arena<T>, but that makes it difficult to initialise
    /// the raw allocator before we have to supply the reference to the arena.
    template <typename AllocT>
    class owned {
    public:
        template <typename ...ArgsT>
        explicit owned (ArgsT &&...args)
            : m_store (std::forward<ArgsT> (args)...)
            , m_arena {m_store}
        { ; }

        owned (owned &&rhs)
            : m_store (std::move (rhs.m_store))
            , m_arena (m_store)
        { ; }

        owned& operator= (owned &&rhs)
        {
            m_store = std::move (rhs.m_store);
        }

        owned (owned const&) = delete;
        owned& operator= (owned const&) = delete;

        template <typename T, typename ...ArgsT>
        decltype(auto) acquire (ArgsT &&...args)
        { return m_arena.template acquire<T,ArgsT...> (std::forward<ArgsT> (args)...); }

        template <typename ...ArgsT>
        decltype(auto) release (ArgsT &&...args)
        { return m_arena.release (std::forward<ArgsT> (args)...); }

        template <typename T, typename ...ArgsT>
        decltype(auto) unique (ArgsT &&...args)
        { return m_arena.template unique<T,ArgsT...> (std::forward<ArgsT> (args)...); }

        template <typename ...Args>
        decltype(auto) reset (Args&&...args)
        { return m_store.reset (std::forward<Args> (args)...); }

        auto const& store (void) const& { return m_store; }
        auto      & store (void)      & { return m_store; }

    private:
        AllocT m_store;
        arena<AllocT> m_arena;
    };
}

#endif