//
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot 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/beast
//

#ifndef BOOST_BEAST_IMPL_FLAT_BUFFER_HPP
#define BOOST_BEAST_IMPL_FLAT_BUFFER_HPP

#include <boost/assert.hpp>
#include <boost/throw_exception.hpp>
#include <stdexcept>

namespace boost {
namespace beast {

/*  Memory is laid out thusly:

    begin_ ..|.. in_ ..|.. out_ ..|.. last_ ..|.. end_
*/

template<class Allocator>
basic_flat_buffer<Allocator>::
~basic_flat_buffer()
{
    if(begin_)
        alloc_traits::deallocate(
            this->member(), begin_, dist(begin_, end_));
}

template<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer()
    : begin_(nullptr)
    , in_(nullptr)
    , out_(nullptr)
    , last_(nullptr)
    , end_(nullptr)
    , max_((std::numeric_limits<std::size_t>::max)())
{
}

template<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(std::size_t limit)
    : begin_(nullptr)
    , in_(nullptr)
    , out_(nullptr)
    , last_(nullptr)
    , end_(nullptr)
    , max_(limit)
{
}

template<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(Allocator const& alloc)
    : detail::empty_base_optimization<base_alloc_type>(alloc)
    , begin_(nullptr)
    , in_(nullptr)
    , out_(nullptr)
    , last_(nullptr)
    , end_(nullptr)
    , max_((std::numeric_limits<std::size_t>::max)())
{
}

template<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(std::size_t limit, Allocator const& alloc)
    : detail::empty_base_optimization<base_alloc_type>(alloc)
    , begin_(nullptr)
    , in_(nullptr)
    , out_(nullptr)
    , last_(nullptr)
    , end_(nullptr)
    , max_(limit)
{
}

template<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(basic_flat_buffer&& other)
    : detail::empty_base_optimization<base_alloc_type>(
        std::move(other.member()))
    , begin_(other.begin_)
    , in_(other.in_)
    , out_(other.out_)
    , last_(out_)
    , end_(other.end_)
    , max_(other.max_)
{
    other.begin_ = nullptr;
    other.in_ = nullptr;
    other.out_ = nullptr;
    other.last_ = nullptr;
    other.end_ = nullptr;
}

template<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(basic_flat_buffer&& other,
        Allocator const& alloc)
    : detail::empty_base_optimization<base_alloc_type>(alloc)
{
    if(this->member() != other.member())
    {
        begin_ = nullptr;
        in_ = nullptr;
        out_ = nullptr;
        last_ = nullptr;
        end_ = nullptr;
        max_ = other.max_;
        copy_from(other);
        other.reset();
    }
    else
    {
        begin_ = other.begin_;
        in_ = other.in_;
        out_ = other.out_;
        last_ = out_;
        end_ = other.end_;
        max_ = other.max_;
        other.begin_ = nullptr;
        other.in_ = nullptr;
        other.out_ = nullptr;
        other.last_ = nullptr;
        other.end_ = nullptr;
    }
}

template<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(basic_flat_buffer const& other)
    : detail::empty_base_optimization<base_alloc_type>(
        alloc_traits::select_on_container_copy_construction(
            other.member()))
    , begin_(nullptr)
    , in_(nullptr)
    , out_(nullptr)
    , last_(nullptr)
    , end_(nullptr)
    , max_(other.max_)
{
    copy_from(other);
}

template<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(basic_flat_buffer const& other,
        Allocator const& alloc)
    : detail::empty_base_optimization<base_alloc_type>(alloc)
    , begin_(nullptr)
    , in_(nullptr)
    , out_(nullptr)
    , last_(nullptr)
    , end_(nullptr)
    , max_(other.max_)
{
    copy_from(other);
}

template<class Allocator>
template<class OtherAlloc>
basic_flat_buffer<Allocator>::
basic_flat_buffer(
        basic_flat_buffer<OtherAlloc> const& other)
    : begin_(nullptr)
    , in_(nullptr)
    , out_(nullptr)
    , last_(nullptr)
    , end_(nullptr)
    , max_(other.max_)
{
    copy_from(other);
}

template<class Allocator>
template<class OtherAlloc>
basic_flat_buffer<Allocator>::
basic_flat_buffer(basic_flat_buffer<OtherAlloc> const& other,
        Allocator const& alloc)
    : detail::empty_base_optimization<base_alloc_type>(alloc)
    , begin_(nullptr)
    , in_(nullptr)
    , out_(nullptr)
    , last_(nullptr)
    , end_(nullptr)
    , max_(other.max_)
{
    copy_from(other);
}

template<class Allocator>
auto
basic_flat_buffer<Allocator>::
operator=(basic_flat_buffer&& other) ->
    basic_flat_buffer&
{
    if(this != &other)
        move_assign(other, std::integral_constant<bool,
            alloc_traits::propagate_on_container_move_assignment::value>{});
    return *this;
}

template<class Allocator>
auto
basic_flat_buffer<Allocator>::
operator=(basic_flat_buffer const& other) ->
    basic_flat_buffer&
{
    if(this != &other)
        copy_assign(other, std::integral_constant<bool,
            alloc_traits::propagate_on_container_copy_assignment::value>{});
    return *this;
}

template<class Allocator>
template<class OtherAlloc>
auto
basic_flat_buffer<Allocator>::
operator=(basic_flat_buffer<OtherAlloc> const& other) ->
    basic_flat_buffer&
{
    reset();
    max_ = other.max_;
    copy_from(other);
    return *this;
}

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

template<class Allocator>
auto
basic_flat_buffer<Allocator>::
prepare(std::size_t n) ->
    mutable_buffers_type
{
    if(n <= dist(out_, end_))
    {
        // existing capacity is sufficient
        last_ = out_ + n;
        return{out_, n};
    }
    auto const len = size();
    if(n <= capacity() - len)
    {
        // after a memmove,
        // existing capacity is sufficient
        if(len > 0)
            std::memmove(begin_, in_, len);
        in_ = begin_;
        out_ = in_ + len;
        last_ = out_ + n;
        return {out_, n};
    }
    // enforce maximum capacity
    if(n > max_ - len)
        BOOST_THROW_EXCEPTION(std::length_error{
            "basic_flat_buffer overflow"});
    // allocate a new buffer
    auto const new_size = (std::min<std::size_t>)(
        max_,
        (std::max<std::size_t>)(2 * len, len + n));
    auto const p = alloc_traits::allocate(
        this->member(), new_size);
    if(begin_)
    {
        BOOST_ASSERT(p);
        BOOST_ASSERT(in_);
        std::memcpy(p, in_, len);
        alloc_traits::deallocate(
            this->member(), begin_, capacity());
    }
    begin_ = p;
    in_ = begin_;
    out_ = in_ + len;
    last_ = out_ + n;
    end_ = begin_ + new_size;
    return {out_, n};
}

template<class Allocator>
void
basic_flat_buffer<Allocator>::
consume(std::size_t n)
{
    if(n >= dist(in_, out_))
    {
        in_ = begin_;
        out_ = begin_;
        return;
    }
    in_ += n;
}

template<class Allocator>
void
basic_flat_buffer<Allocator>::
shrink_to_fit()
{
    auto const len = size();
    if(len == capacity())
        return;
    char* p;
    if(len > 0)
    {
        BOOST_ASSERT(begin_);
        BOOST_ASSERT(in_);
        p = alloc_traits::allocate(
            this->member(), len);
        std::memcpy(p, in_, len);
    }
    else
    {
        p = nullptr;
    }
    alloc_traits::deallocate(
        this->member(), begin_, dist(begin_, end_));
    begin_ = p;
    in_ = begin_;
    out_ = begin_ + len;
    last_ = out_;
    end_ = out_;
}

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

template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
reset()
{
    consume(size());
    shrink_to_fit();
}

template<class Allocator>
template<class DynamicBuffer>
inline
void
basic_flat_buffer<Allocator>::
copy_from(DynamicBuffer const& buffer)
{
    if(buffer.size() == 0)
        return;
    using boost::asio::buffer_copy;
    commit(buffer_copy(
        prepare(buffer.size()), buffer.data()));
}

template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
move_assign(basic_flat_buffer& other, std::true_type)
{
    reset();
    this->member() = std::move(other.member());
    begin_ = other.begin_;
    in_ = other.in_;
    out_ = other.out_;
    last_ = out_;
    end_ = other.end_;
    max_ = other.max_;
    other.begin_ = nullptr;
    other.in_ = nullptr;
    other.out_ = nullptr;
    other.last_ = nullptr;
    other.end_ = nullptr;
}

template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
move_assign(basic_flat_buffer& other, std::false_type)
{
    reset();
    if(this->member() != other.member())
    {
        copy_from(other);
        other.reset();
    }
    else
    {
        move_assign(other, std::true_type{});
    }
}

template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
copy_assign(basic_flat_buffer const& other, std::true_type)
{
    reset();
    max_ = other.max_;
    this->member() = other.member();
    copy_from(other);
}

template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
copy_assign(basic_flat_buffer const& other, std::false_type)
{
    reset();
    max_ = other.max_;
    copy_from(other);
}

template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
swap(basic_flat_buffer& other)
{
    swap(other, typename
        alloc_traits::propagate_on_container_swap{});
}

template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
swap(basic_flat_buffer& other, std::true_type)
{
    using std::swap;
    swap(this->member(), other.member());
    swap(max_, other.max_);
    swap(begin_, other.begin_);
    swap(in_, other.in_);
    swap(out_, other.out_);
    last_ = this->out_;
    other.last_ = other.out_;
    swap(end_, other.end_);
}

template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
swap(basic_flat_buffer& other, std::false_type)
{
    BOOST_ASSERT(this->member() == other.member());
    using std::swap;
    swap(max_, other.max_);
    swap(begin_, other.begin_);
    swap(in_, other.in_);
    swap(out_, other.out_);
    last_ = this->out_;
    other.last_ = other.out_;
    swap(end_, other.end_);
}

template<class Allocator>
void
swap(
    basic_flat_buffer<Allocator>& lhs,
    basic_flat_buffer<Allocator>& rhs)
{
    lhs.swap(rhs);
}

} // beast
} // boost

#endif
