//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2020 Krystian Stasiowski (sdkrystian@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/json
//

#ifndef BOOST_JSON_IMPL_MONOTONIC_RESOURCE_IPP
#define BOOST_JSON_IMPL_MONOTONIC_RESOURCE_IPP

#include <boost/json/monotonic_resource.hpp>
#include <boost/json/detail/except.hpp>
#include <boost/align/align.hpp>
#include <boost/core/max_align.hpp>

#include <memory>

namespace boost {
namespace json {

struct alignas(core::max_align_t)
    monotonic_resource::block : block_base
{
};

constexpr
std::size_t
monotonic_resource::
max_size()
{
    return std::size_t(-1) - sizeof(block);
}

// lowest power of 2 greater than or equal to n
std::size_t
monotonic_resource::
round_pow2(
    std::size_t n) noexcept
{
    if(n & (n - 1))
        return next_pow2(n);
    return n;
}

// lowest power of 2 greater than n
std::size_t
monotonic_resource::
next_pow2(
    std::size_t n) noexcept
{
    std::size_t result = min_size_;
    while(result <= n)
    {
        if(result >= max_size() - result)
        {
            // overflow
            result = max_size();
            break;
        }
        result *= 2;
    }
    return result;
}

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

monotonic_resource::
~monotonic_resource()
{
    release();
}

monotonic_resource::
monotonic_resource(
    std::size_t initial_size,
    storage_ptr upstream) noexcept
    : buffer_{
        nullptr, 0, 0, nullptr}
    , next_size_(round_pow2(initial_size))
    , upstream_(std::move(upstream))
{
}

monotonic_resource::
monotonic_resource(
    unsigned char* buffer,
    std::size_t size,
    storage_ptr upstream) noexcept
    : buffer_{
        buffer, size, size, nullptr}
    , next_size_(next_pow2(size))
    , upstream_(std::move(upstream))
{
}

void
monotonic_resource::
release() noexcept
{
    auto p = head_;
    while(p != &buffer_)
    {
        auto next = p->next;
        upstream_->deallocate(p, p->size);
        p = next;
    }
    buffer_.p = reinterpret_cast<
        unsigned char*>(buffer_.p) - (
            buffer_.size - buffer_.avail);
    buffer_.avail = buffer_.size;
    head_ = &buffer_;
}

void*
monotonic_resource::
do_allocate(
    std::size_t n,
    std::size_t align)
{
    auto p = alignment::align(
        align, n, head_->p, head_->avail);
    if(p)
    {
        head_->p = reinterpret_cast<
            unsigned char*>(p) + n;
        head_->avail -= n;
        return p;
    }

    if(next_size_ < n)
        next_size_ = round_pow2(n);
    auto b = ::new(upstream_->allocate(
        sizeof(block) + next_size_)) block;
    b->p = b + 1;
    b->avail = next_size_;
    b->size = next_size_;
    b->next = head_;
    head_ = b;
    next_size_ = next_pow2(next_size_);

    p = alignment::align(
        align, n, head_->p, head_->avail);
    BOOST_ASSERT(p);
    head_->p = reinterpret_cast<
        unsigned char*>(p) + n;
    head_->avail -= n;
    return p;
}

void
monotonic_resource::
do_deallocate(
    void*,
    std::size_t,
    std::size_t)
{
    // do nothing
}

bool
monotonic_resource::
do_is_equal(
    memory_resource const& mr) const noexcept
{
    return this == &mr;
}

} // namespace json
} // namespace boost

#endif