//
// 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_MULTI_BUFFER_HPP
#define BOOST_BEAST_MULTI_BUFFER_HPP

#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/detail/allocator.hpp>
#include <boost/beast/core/detail/empty_base_optimization.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/intrusive/list.hpp>
#include <iterator>
#include <limits>
#include <memory>
#include <type_traits>

namespace boost {
namespace beast {

/** A @b DynamicBuffer that uses multiple buffers internally.

    The implementation uses a sequence of one or more character arrays
    of varying sizes. Additional character array objects are appended to
    the sequence to accommodate changes in the size of the character
    sequence.

    @note Meets the requirements of @b DynamicBuffer.

    @tparam Allocator The allocator to use for managing memory.
*/
template<class Allocator>
class basic_multi_buffer
#if ! BOOST_BEAST_DOXYGEN
    : private detail::empty_base_optimization<
        typename detail::allocator_traits<Allocator>::
            template rebind_alloc<char>>
#endif
{
    using base_alloc_type = typename
        detail::allocator_traits<Allocator>::
            template rebind_alloc<char>;

    // Storage for the list of buffers representing the input
    // and output sequences. The allocation for each element
    // contains `element` followed by raw storage bytes.
    class element;

    using alloc_traits = detail::allocator_traits<base_alloc_type>;
    using list_type = typename boost::intrusive::make_list<element,
        boost::intrusive::constant_time_size<true>>::type;
    using iter = typename list_type::iterator;
    using const_iter = typename list_type::const_iterator;

    using size_type = typename alloc_traits::size_type;
    using const_buffer = boost::asio::const_buffer;
    using mutable_buffer = boost::asio::mutable_buffer;

    static_assert(std::is_base_of<std::bidirectional_iterator_tag,
        typename std::iterator_traits<iter>::iterator_category>::value,
            "BidirectionalIterator requirements not met");

    static_assert(std::is_base_of<std::bidirectional_iterator_tag,
        typename std::iterator_traits<const_iter>::iterator_category>::value,
            "BidirectionalIterator requirements not met");

    std::size_t max_ =
        (std::numeric_limits<std::size_t>::max)();
    list_type list_;        // list of allocated buffers
    iter out_;              // element that contains out_pos_
    size_type in_size_ = 0; // size of the input sequence
    size_type in_pos_ = 0;  // input offset in list_.front()
    size_type out_pos_ = 0; // output offset in *out_
    size_type out_end_ = 0; // output end offset in list_.back()

public:
    /// The type of allocator used.
    using allocator_type = Allocator;

#if BOOST_BEAST_DOXYGEN
    /// The type used to represent the input sequence as a list of buffers.
    using const_buffers_type = implementation_defined;

    /// The type used to represent the output sequence as a list of buffers.
    using mutable_buffers_type = implementation_defined;

#else
    class const_buffers_type;

    class mutable_buffers_type;

#endif

    /// Destructor
    ~basic_multi_buffer();

    /** Constructor

        Upon construction, capacity will be zero.
    */
    basic_multi_buffer();

    /** Constructor.

        @param limit The setting for @ref max_size.
    */
    explicit
    basic_multi_buffer(std::size_t limit);

    /** Constructor.

        @param alloc The allocator to use.
    */
    explicit
    basic_multi_buffer(Allocator const& alloc);

    /** Constructor.

        @param limit The setting for @ref max_size.

        @param alloc The allocator to use.
    */
    basic_multi_buffer(
        std::size_t limit, Allocator const& alloc);

    /** Move constructor

        After the move, `*this` will have an empty output sequence.

        @param other The object to move from. After the move,
        The object's state will be as if constructed using
        its current allocator and limit.
    */
    basic_multi_buffer(basic_multi_buffer&& other);

    /** Move constructor

        After the move, `*this` will have an empty output sequence.

        @param other The object to move from. After the move,
        The object's state will be as if constructed using
        its current allocator and limit.

        @param alloc The allocator to use.
    */
    basic_multi_buffer(basic_multi_buffer&& other,
        Allocator const& alloc);

    /** Copy constructor.

        @param other The object to copy from.
    */
    basic_multi_buffer(basic_multi_buffer const& other);

    /** Copy constructor

        @param other The object to copy from.

        @param alloc The allocator to use.
    */
    basic_multi_buffer(basic_multi_buffer const& other,
        Allocator const& alloc);

    /** Copy constructor.

        @param other The object to copy from.
    */
    template<class OtherAlloc>
    basic_multi_buffer(basic_multi_buffer<
        OtherAlloc> const& other);

    /** Copy constructor.

        @param other The object to copy from.

        @param alloc The allocator to use.
    */
    template<class OtherAlloc>
    basic_multi_buffer(basic_multi_buffer<
        OtherAlloc> const& other, allocator_type const& alloc);

    /** Move assignment

        After the move, `*this` will have an empty output sequence.

        @param other The object to move from. After the move,
        The object's state will be as if constructed using
        its current allocator and limit.
    */
    basic_multi_buffer&
    operator=(basic_multi_buffer&& other);

    /** Copy assignment

        After the copy, `*this` will have an empty output sequence.

        @param other The object to copy from.
    */
    basic_multi_buffer& operator=(basic_multi_buffer const& other);

    /** Copy assignment

        After the copy, `*this` will have an empty output sequence.

        @param other The object to copy from.
    */
    template<class OtherAlloc>
    basic_multi_buffer& operator=(
        basic_multi_buffer<OtherAlloc> const& other);

    /// Returns a copy of the associated allocator.
    allocator_type
    get_allocator() const
    {
        return this->member();
    }

    /// Returns the size of the input sequence.
    size_type
    size() const
    {
        return in_size_;
    }

    /// Returns the permitted maximum sum of the sizes of the input and output sequence.
    size_type
    max_size() const
    {
        return max_;
    }

    /// Returns the maximum sum of the sizes of the input sequence and output sequence the buffer can hold without requiring reallocation.
    std::size_t
    capacity() const;

    /** Get a list of buffers that represents the input sequence.

        @note These buffers remain valid across subsequent calls to `prepare`.
    */
    const_buffers_type
    data() const;

    /** Get a list of buffers that represents the output sequence, with the given size.

        @note Buffers representing the input sequence acquired prior to
        this call remain valid.
    */
    mutable_buffers_type
    prepare(size_type n);

    /** Move bytes from the output sequence to the input sequence.

        @note Buffers representing the input sequence acquired prior to
        this call remain valid.
    */
    void
    commit(size_type n);

    /// Remove bytes from the input sequence.
    void
    consume(size_type n);

    template<class Alloc>
    friend
    void
    swap(
        basic_multi_buffer<Alloc>& lhs,
        basic_multi_buffer<Alloc>& rhs);

private:
    template<class OtherAlloc>
    friend class basic_multi_buffer;

    void
    delete_list();

    void
    reset();

    template<class DynamicBuffer>
    void
    copy_from(DynamicBuffer const& other);

    void
    move_assign(basic_multi_buffer& other, std::false_type);

    void
    move_assign(basic_multi_buffer& other, std::true_type);

    void
    copy_assign(basic_multi_buffer const& other, std::false_type);

    void
    copy_assign(basic_multi_buffer const& other, std::true_type);

    void
    swap(basic_multi_buffer&);

    void
    swap(basic_multi_buffer&, std::true_type);

    void
    swap(basic_multi_buffer&, std::false_type);

    void
    debug_check() const;
};

/// A typical multi buffer
using multi_buffer = basic_multi_buffer<std::allocator<char>>;

} // beast
} // boost

#include <boost/beast/core/impl/multi_buffer.ipp>

#endif
