//
// 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_PREFIX_HPP
#define BOOST_BEAST_IMPL_BUFFERS_PREFIX_HPP

#include <boost/beast/core/buffer_traits.hpp>
#include <boost/config/workaround.hpp>
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <stdexcept>
#include <type_traits>
#include <utility>

namespace boost {
namespace beast {

template<class Buffers>
class buffers_prefix_view<Buffers>::const_iterator
{
    friend class buffers_prefix_view<Buffers>;

    buffers_prefix_view const* b_ = nullptr;
    std::size_t remain_ = 0;
    iter_type it_{};

public:
#if BOOST_WORKAROUND(BOOST_MSVC, < 1910)
    using value_type = typename std::conditional<
        boost::is_convertible<typename
            std::iterator_traits<iter_type>::value_type,
                net::mutable_buffer>::value,
                    net::mutable_buffer,
                        net::const_buffer>::type;
#else
    using value_type = buffers_type<Buffers>;
#endif

    BOOST_CORE_STATIC_ASSERT(std::is_same<
        typename const_iterator::value_type,
        typename buffers_prefix_view::value_type>::value);

    using pointer = value_type const*;
    using reference = value_type;
    using difference_type = std::ptrdiff_t;
    using iterator_category =
        std::bidirectional_iterator_tag;

    const_iterator() = default;
    const_iterator(
        const_iterator const& other) = default;
    const_iterator& operator=(
        const_iterator const& other) = default;

    bool
    operator==(const_iterator const& other) const
    {
        return b_ == other.b_ && it_ == other.it_;
    }

    bool
    operator!=(const_iterator const& other) const
    {
        return !(*this == other);
    }

    reference
    operator*() const
    {
        value_type v(*it_);
        if(remain_ < v.size())
            return {v.data(), remain_};
        return v;
    }

    pointer
    operator->() const = delete;

    const_iterator&
    operator++()
    {
        value_type const v = *it_++;
        remain_ -= v.size();
        return *this;
    }

    const_iterator
    operator++(int)
    {
        auto temp = *this;
        value_type const v = *it_++;
        remain_ -= v.size();
        return temp;
    }

    const_iterator&
    operator--()
    {
        value_type const v = *--it_;
        remain_ += v.size();
        return *this;
    }

    const_iterator
    operator--(int)
    {
        auto temp = *this;
        value_type const v = *--it_;
        remain_ += v.size();
        return temp;
    }

private:
    const_iterator(
        buffers_prefix_view const& b,
        std::true_type)
        : b_(&b)
        , remain_(b.remain_)
        , it_(b_->end_)
    {
    }

    const_iterator(
        buffers_prefix_view const& b,
        std::false_type)
        : b_(&b)
        , remain_(b_->size_)
        , it_(net::buffer_sequence_begin(b_->bs_))
    {
    }
};

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

template<class Buffers>
void
buffers_prefix_view<Buffers>::
setup(std::size_t size)
{
    size_ = 0;
    remain_ = 0;
    end_ = net::buffer_sequence_begin(bs_);
    auto const last = bs_.end();
    while(end_ != last)
    {
        auto const len = buffer_bytes(*end_++);
        if(len >= size)
        {
            size_ += size;

            // by design, this subtraction can wrap
            BOOST_CORE_STATIC_ASSERT(std::is_unsigned<
                decltype(remain_)>::value);
            remain_ = size - len;
            break;
        }
        size -= len;
        size_ += len;
    }
}

template<class Buffers>
buffers_prefix_view<Buffers>::
buffers_prefix_view(
    buffers_prefix_view const& other,
    std::size_t dist)
    : bs_(other.bs_)
    , size_(other.size_)
    , remain_(other.remain_)
    , end_(std::next(bs_.begin(), dist))
{
}

template<class Buffers>
buffers_prefix_view<Buffers>::
buffers_prefix_view(buffers_prefix_view const& other)
    : buffers_prefix_view(other,
        std::distance<iter_type>(
            net::buffer_sequence_begin(other.bs_),
                other.end_))
{
}

template<class Buffers>
auto
buffers_prefix_view<Buffers>::
operator=(buffers_prefix_view const& other) ->
    buffers_prefix_view&
{
    auto const dist = std::distance<iter_type>(
        net::buffer_sequence_begin(other.bs_),
        other.end_);
    bs_ = other.bs_;
    size_ = other.size_;
    remain_ = other.remain_;
    end_ = std::next(
        net::buffer_sequence_begin(bs_),
            dist);
    return *this;
}

template<class Buffers>
buffers_prefix_view<Buffers>::
buffers_prefix_view(
    std::size_t size,
    Buffers const& bs)
    : bs_(bs)
{
    setup(size);
}

template<class Buffers>
template<class... Args>
buffers_prefix_view<Buffers>::
buffers_prefix_view(
    std::size_t size,
    boost::in_place_init_t,
    Args&&... args)
    : bs_(std::forward<Args>(args)...)
{
    setup(size);
}

template<class Buffers>
auto
buffers_prefix_view<Buffers>::
begin() const ->
    const_iterator
{
    return const_iterator{
        *this, std::false_type{}};
}

template<class Buffers>
auto
buffers_prefix_view<Buffers>::
end() const ->
    const_iterator
{
    return const_iterator{
        *this, std::true_type{}};
}

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

template<>
class buffers_prefix_view<net::const_buffer>
    : public net::const_buffer
{
public:
    using net::const_buffer::const_buffer;
    buffers_prefix_view(buffers_prefix_view const&) = default;
    buffers_prefix_view& operator=(buffers_prefix_view const&) = default;

    buffers_prefix_view(
        std::size_t size,
        net::const_buffer buffer)
        : net::const_buffer(
            buffer.data(),
            std::min<std::size_t>(size, buffer.size())
        #if defined(BOOST_ASIO_ENABLE_BUFFER_DEBUGGING)
            , buffer.get_debug_check()
        #endif
            )
    {
    }

    template<class... Args>
    buffers_prefix_view(
        std::size_t size,
        boost::in_place_init_t,
        Args&&... args)
        : buffers_prefix_view(size,
            net::const_buffer(
                std::forward<Args>(args)...))
    {
    }
};

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

template<>
class buffers_prefix_view<net::mutable_buffer>
    : public net::mutable_buffer
{
public:
    using net::mutable_buffer::mutable_buffer;
    buffers_prefix_view(buffers_prefix_view const&) = default;
    buffers_prefix_view& operator=(buffers_prefix_view const&) = default;

    buffers_prefix_view(
        std::size_t size,
        net::mutable_buffer buffer)
        : net::mutable_buffer(
            buffer.data(),
            std::min<std::size_t>(size, buffer.size())
        #if defined(BOOST_ASIO_ENABLE_BUFFER_DEBUGGING)
            , buffer.get_debug_check()
        #endif
            )
    {
    }

    template<class... Args>
    buffers_prefix_view(
        std::size_t size,
        boost::in_place_init_t,
        Args&&... args)
        : buffers_prefix_view(size,
            net::mutable_buffer(
                std::forward<Args>(args)...))
    {
    }
};

} // beast
} // boost

#endif
