/*
 * 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>
 */

#pragma once

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

#include <cstddef>
#include <iterator>


namespace cruft::alloc {
    // allocate progressively across a buffer without concern for deallocation.
    // deallocation is a noop; the only way to free allocations is via reset.
    class linear {
    public:
        linear (const linear&) = delete;
        linear& operator= (const linear&) = delete;

        linear (linear&&) noexcept;
        linear& operator= (linear&&) noexcept;

        linear (cruft::view<u08*> _data);

        template <
            typename BufferT,
            typename = std::enable_if_t<
                buffer::is_buffer_v<BufferT>
            >
        >
        linear (BufferT &_buffer)
            : linear (cruft::view (_buffer))
        { ; }


        cruft::view<u08*>
        allocate (size_t bytes)
        {
            if (m_cursor + bytes > m_end)
                throw std::bad_alloc ();

            auto ptr = m_cursor;
            m_cursor += bytes;
            return { ptr, bytes };
        }


        cruft::view<u08*>
        allocate (size_t bytes, std::size_t alignment)
        {
            auto ptr = cruft::align::up (m_cursor, alignment);
            if (ptr + bytes > m_end)
                throw std::bad_alloc ();

            m_cursor = ptr + bytes;

            return { ptr, bytes };
        }


        void deallocate (void *ptr, std::size_t bytes, std::size_t alignment)
        {
            (void)ptr;
            (void)bytes;
            (void)alignment;
        }


        void deallocate (void *ptr, std::size_t bytes)
        {
            return deallocate (ptr, bytes, alignof (std::max_align_t));
        }


        u08* data (void);
        u08* begin (void);
        u08* end (void);
        u08* cursor (void);

        u08 const* data (void) const;
        u08 const* begin (void) const;
        u08 const* end (void) const;
        u08 const* cursor (void) const;

        size_t offset (const void*) const;

        template <typename ValueT>
        size_t
        offset (ValueT const *ptr) const
        {
            CHECK_MOD (reinterpret_cast<uintptr_t> (ptr),     sizeof (ValueT));
            CHECK_MOD (reinterpret_cast<uintptr_t> (data ()), sizeof (ValueT));

            return ptr - cruft::cast::alignment<ValueT const*> (data ());
        }

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

        void reset (void);

        size_t capacity (void) const;
        size_t used     (void) const;
        size_t remain   (void) const;

    protected:
        // The begin and end iterators should be constant but that interferes
        // with move operators so we need to leave them mutable.
        u08 *m_begin;
        u08 *m_end;

        u08 *m_cursor;
    };
}