//
// Copyright (c) 2016-2019 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_BUFFERS_ADAPTOR_HPP
#define BOOST_BEAST_IMPL_BUFFERS_ADAPTOR_HPP

#include <boost/beast/core/buffer_traits.hpp>
#include <boost/beast/core/buffers_adaptor.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/config/workaround.hpp>
#include <boost/throw_exception.hpp>
#include <algorithm>
#include <cstring>
#include <iterator>
#include <stdexcept>
#include <type_traits>
#include <utility>

namespace boost {
namespace beast {

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

#if BOOST_WORKAROUND(BOOST_MSVC, < 1910)
# pragma warning (push)
# pragma warning (disable: 4521) // multiple copy constructors specified
# pragma warning (disable: 4522) // multiple assignment operators specified
#endif

template<class MutableBufferSequence>
template<bool isMutable>
class buffers_adaptor<MutableBufferSequence>::subrange
{
public:
    using value_type = typename std::conditional<
        isMutable,
        net::mutable_buffer,
        net::const_buffer>::type;

    struct iterator;
    using const_iterator = iterator;

    // construct from two iterators plus optionally subrange definition
    subrange(
        iter_type first,        // iterator to first buffer in storage
        iter_type last,         // iterator to last buffer in storage
        std::size_t pos = 0,    // the offset in bytes from the beginning of the storage
        std::size_t n =         // the total length of the subrange
        (std::numeric_limits<std::size_t>::max)());

#if BOOST_WORKAROUND(BOOST_MSVC, < 1910)
    subrange(
        subrange const& other)
        : first_(other.first_)
        , last_(other.last_)
        , first_offset_(other.first_offset_)
        , last_size_(other.last_size_)
    {
    }

    subrange& operator=(
        subrange const& other)
    {
        first_ = other.first_;
        last_ = other.last_;
        first_offset_ = other.first_offset_;
        last_size_ = other.last_size_;
        return *this;
    }
#else
    subrange(
        subrange const&) = default;
    subrange& operator=(
        subrange const&) = default;
#endif

    // allow conversion from mutable to const
    template<bool isMutable_ = isMutable, typename
        std::enable_if<!isMutable_>::type * = nullptr>
    subrange(subrange<true> const &other)
        : first_(other.first_)
        , last_(other.last_)
        , first_offset_(other.first_offset_)
        , last_size_(other.last_size_)
    {
    }

    iterator
    begin() const;

    iterator
    end() const;

private:

    friend subrange<!isMutable>;

    void
    adjust(
        std::size_t pos,
        std::size_t n);

private:
    // points to the first buffer in the sequence
    iter_type first_;

    // Points to one past the end of the underlying buffer sequence
    iter_type last_;

    // The initial offset into the first buffer
    std::size_t first_offset_;

    // how many bytes in the penultimate buffer are used (if any)
    std::size_t last_size_;
};

#if BOOST_WORKAROUND(BOOST_MSVC, < 1910)
# pragma warning (pop)
#endif

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

template<class MutableBufferSequence>
template<bool isMutable>
struct buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
    iterator
{
    using iterator_category = std::bidirectional_iterator_tag;
    using value_type = typename
        buffers_adaptor<MutableBufferSequence>::
        template subrange<isMutable>::
        value_type;
    using reference = value_type&;
    using pointer = value_type*;
    using difference_type = std::ptrdiff_t;

    iterator(
        subrange<isMutable> const *parent,
        iter_type it);

    iterator();

    value_type
    operator*() const;

    pointer
    operator->() const = delete;

    iterator &
    operator++();

    iterator
    operator++(int);

    iterator &
    operator--();

    iterator
    operator--(int);

    bool
    operator==(iterator const &b) const;

    bool
    operator!=(iterator const &b) const;

private:

    subrange<isMutable> const *parent_;
    iter_type it_;
};

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

template<class MutableBufferSequence>
auto
buffers_adaptor<MutableBufferSequence>::
end_impl() const ->
    iter_type
{
    return out_ == end_ ? end_ : std::next(out_);
}

template<class MutableBufferSequence>
buffers_adaptor<MutableBufferSequence>::
buffers_adaptor(
    buffers_adaptor const& other,
    std::size_t nbegin,
    std::size_t nout,
    std::size_t nend)
    : bs_(other.bs_)
    , begin_(std::next(net::buffer_sequence_begin(bs_), nbegin))
    , out_(std::next(net::buffer_sequence_begin(bs_), nout))
    , end_(std::next(net::buffer_sequence_begin(bs_), nend))
    , max_size_(other.max_size_)
    , in_pos_(other.in_pos_)
    , in_size_(other.in_size_)
    , out_pos_(other.out_pos_)
    , out_end_(other.out_end_)
{
}

template<class MutableBufferSequence>
buffers_adaptor<MutableBufferSequence>::
buffers_adaptor(MutableBufferSequence const& bs)
    : bs_(bs)
    , begin_(net::buffer_sequence_begin(bs_))
    , out_  (net::buffer_sequence_begin(bs_))
    , end_  (net::buffer_sequence_begin(bs_))
    , max_size_(
        [&bs]
        {
            return buffer_bytes(bs);
        }())
{
}

template<class MutableBufferSequence>
template<class... Args>
buffers_adaptor<MutableBufferSequence>::
buffers_adaptor(
    boost::in_place_init_t, Args&&... args)
    : bs_{std::forward<Args>(args)...}
    , begin_(net::buffer_sequence_begin(bs_))
    , out_  (net::buffer_sequence_begin(bs_))
    , end_  (net::buffer_sequence_begin(bs_))
    , max_size_(
        [&]
        {
            return buffer_bytes(bs_);
        }())
{
}

template<class MutableBufferSequence>
buffers_adaptor<MutableBufferSequence>::
buffers_adaptor(buffers_adaptor const& other)
    : buffers_adaptor(
        other,
        std::distance<iter_type>(
            net::buffer_sequence_begin(other.bs_),
            other.begin_),
        std::distance<iter_type>(
            net::buffer_sequence_begin(other.bs_),
            other.out_),
        std::distance<iter_type>(
            net::buffer_sequence_begin(other.bs_),
            other.end_))
{
}

template<class MutableBufferSequence>
auto
buffers_adaptor<MutableBufferSequence>::
operator=(buffers_adaptor const& other) ->
    buffers_adaptor&
{
    if(this == &other)
        return *this;
    auto const nbegin = std::distance<iter_type>(
        net::buffer_sequence_begin(other.bs_),
            other.begin_);
    auto const nout = std::distance<iter_type>(
        net::buffer_sequence_begin(other.bs_),
            other.out_);
    auto const nend = std::distance<iter_type>(
        net::buffer_sequence_begin(other.bs_),
            other.end_);
    bs_ = other.bs_;
    begin_ = std::next(
        net::buffer_sequence_begin(bs_), nbegin);
    out_ =   std::next(
        net::buffer_sequence_begin(bs_), nout);
    end_ =   std::next(
        net::buffer_sequence_begin(bs_), nend);
    max_size_ = other.max_size_;
    in_pos_ = other.in_pos_;
    in_size_ = other.in_size_;
    out_pos_ = other.out_pos_;
    out_end_ = other.out_end_;
    return *this;
}

//

template<class MutableBufferSequence>
auto
buffers_adaptor<MutableBufferSequence>::
data() const noexcept ->
    const_buffers_type
{
    return const_buffers_type(
        begin_, end_,
        in_pos_, in_size_);
}

template<class MutableBufferSequence>
auto
buffers_adaptor<MutableBufferSequence>::
data() noexcept ->
    mutable_buffers_type
{
    return mutable_buffers_type(
        begin_, end_,
        in_pos_, in_size_);
}

template<class MutableBufferSequence>
auto
buffers_adaptor<MutableBufferSequence>::
prepare(std::size_t n) ->
    mutable_buffers_type
{
    auto prepared = n;
    end_ = out_;
    if(end_ != net::buffer_sequence_end(bs_))
    {
        auto size = buffer_bytes(*end_) - out_pos_;
        if(n > size)
        {
            n -= size;
            while(++end_ !=
                net::buffer_sequence_end(bs_))
            {
                size = buffer_bytes(*end_);
                if(n < size)
                {
                    out_end_ = n;
                    n = 0;
                    ++end_;
                    break;
                }
                n -= size;
                out_end_ = size;
            }
        }
        else
        {
            ++end_;
            out_end_ = out_pos_ + n;
            n = 0;
        }
    }
    if(n > 0)
        BOOST_THROW_EXCEPTION(std::length_error{
            "buffers_adaptor too long"});
    return mutable_buffers_type(out_, end_, out_pos_, prepared);
}

template<class MutableBufferSequence>
void
buffers_adaptor<MutableBufferSequence>::
commit(std::size_t n) noexcept
{
    if(out_ == end_)
        return;
    auto const last = std::prev(end_);
    while(out_ != last)
    {
        auto const avail =
            buffer_bytes(*out_) - out_pos_;
        if(n < avail)
        {
            out_pos_ += n;
            in_size_ += n;
            return;
        }
        ++out_;
        n -= avail;
        out_pos_ = 0;
        in_size_ += avail;
    }

    n = std::min<std::size_t>(
        n, out_end_ - out_pos_);
    out_pos_ += n;
    in_size_ += n;
    if(out_pos_ == buffer_bytes(*out_))
    {
        ++out_;
        out_pos_ = 0;
        out_end_ = 0;
    }
}

template<class MutableBufferSequence>
void
buffers_adaptor<MutableBufferSequence>::
consume(std::size_t n) noexcept
{
    while(begin_ != out_)
    {
        auto const avail =
            buffer_bytes(*begin_) - in_pos_;
        if(n < avail)
        {
            in_size_ -= n;
            in_pos_ += n;
            return;
        }
        n -= avail;
        in_size_ -= avail;
        in_pos_ = 0;
        ++begin_;
    }
    auto const avail = out_pos_ - in_pos_;
    if(n < avail)
    {
        in_size_ -= n;
        in_pos_ += n;
    }
    else
    {
        in_size_ -= avail;
        in_pos_ = out_pos_;
    }
}

template<class MutableBufferSequence>
auto
buffers_adaptor<MutableBufferSequence>::
    make_subrange(std::size_t pos, std::size_t n) ->
        subrange<true>
{
    return subrange<true>(
        begin_, net::buffer_sequence_end(bs_),
        in_pos_ + pos, n);
}

template<class MutableBufferSequence>
auto
buffers_adaptor<MutableBufferSequence>::
    make_subrange(std::size_t pos, std::size_t n) const ->
        subrange<false>
{
    return subrange<false>(
        begin_, net::buffer_sequence_end(bs_),
        in_pos_ + pos, n);
}

// -------------------------------------------------------------------------
// subrange

template<class MutableBufferSequence>
template<bool isMutable>
buffers_adaptor<MutableBufferSequence>::
subrange<isMutable>::
subrange(
    iter_type first,  // iterator to first buffer in storage
    iter_type last,   // iterator to last buffer in storage
    std::size_t pos,        // the offset in bytes from the beginning of the storage
    std::size_t n)         // the total length of the subrange
    : first_(first)
    , last_(last)
    , first_offset_(0)
    , last_size_((std::numeric_limits<std::size_t>::max)())
{
    adjust(pos, n);
}

template<class MutableBufferSequence>
template<bool isMutable>
void
buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
adjust(
    std::size_t pos,
    std::size_t n)
{
    if (n == 0)
        last_ = first_;

    if (first_ == last_)
    {
        first_offset_ = 0;
        last_size_ = 0;
        return;
    }

    auto is_last = [this](iter_type iter) {
        return std::next(iter) == last_;
    };


    pos += first_offset_;
    while (pos)
    {
        auto adjust = (std::min)(pos, first_->size());
        if (adjust >= first_->size())
        {
            ++first_;
            first_offset_ = 0;
            pos -= adjust;
        }
        else
        {
            first_offset_ = adjust;
            pos = 0;
            break;
        }
    }

    auto current = first_;
    auto max_elem = current->size() - first_offset_;
    if (is_last(current))
    {
        // both first and last element
        last_size_ = (std::min)(max_elem, n);
        last_ = std::next(current);
        return;
    }
    else if (max_elem >= n)
    {
        last_ = std::next(current);
        last_size_ = n;
    }
    else
    {
        n -= max_elem;
        ++current;
    }

    for (;;)
    {
        max_elem = current->size();
        if (is_last(current))
        {
            last_size_ = (std::min)(n, last_size_);
            return;
        }
        else if (max_elem < n)
        {
            n -= max_elem;
            ++current;
        }
        else
        {
            last_size_ = n;
            last_ = std::next(current);
            return;
        }
    }
}


template<class MutableBufferSequence>
template<bool isMutable>
auto
buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
begin() const ->
iterator
{
    return iterator(this, first_);
}

template<class MutableBufferSequence>
template<bool isMutable>
auto
buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
end() const ->
iterator
{
    return iterator(this, last_);
}

// -------------------------------------------------------------------------
// buffers_adaptor::subrange::iterator

template<class MutableBufferSequence>
template<bool isMutable>
buffers_adaptor<MutableBufferSequence>::
subrange<isMutable>::
iterator::
iterator()
    : parent_(nullptr)
    , it_()
{
}

template<class MutableBufferSequence>
template<bool isMutable>
buffers_adaptor<MutableBufferSequence>::
subrange<isMutable>::
iterator::
iterator(subrange<isMutable> const *parent,
    iter_type it)
    : parent_(parent)
    , it_(it)
{
}

template<class MutableBufferSequence>
template<bool isMutable>
auto
buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
    iterator::
operator*() const ->
    value_type
{
    value_type result = *it_;

    if (it_ == parent_->first_)
        result += parent_->first_offset_;

    if (std::next(it_) == parent_->last_)
    {
        result = value_type(
            result.data(),
            (std::min)(
                parent_->last_size_,
                result.size()));
    }

    return result;
}

template<class MutableBufferSequence>
template<bool isMutable>
auto
buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
    iterator::
operator++() ->
    iterator &
{
    ++it_;
    return *this;
}

template<class MutableBufferSequence>
template<bool isMutable>
auto
buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
    iterator::
operator++(int) ->
    iterator
{
    auto result = *this;
    ++it_;
    return result;
}

template<class MutableBufferSequence>
template<bool isMutable>
auto
buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
    iterator::
operator--() ->
    iterator &
{
    --it_;
    return *this;
}

template<class MutableBufferSequence>
template<bool isMutable>
auto
buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
    iterator::
operator--(int) ->
    iterator
{
    auto result = *this;
    --it_;
    return result;
}

template<class MutableBufferSequence>
template<bool isMutable>
auto
buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
    iterator::
operator==(iterator const &b) const ->
    bool
{
    return it_ == b.it_;
}

template<class MutableBufferSequence>
template<bool isMutable>
auto
buffers_adaptor<MutableBufferSequence>::
    subrange<isMutable>::
    iterator::
operator!=(iterator const &b) const ->
    bool
{
    return !(*this == b);
}

} // beast
} // boost

#endif
