#pragma once

#include <algorithm>
#include <cstddef>
#include <cstring>

#include <neo/const_buffer.hpp>
#include <neo/mutable_buffer.hpp>

namespace neo {

namespace detail {

void get_buffer_sequence_begin(void*, class buf_seq_begin_opaq);
void get_buffer_sequence_end(void*, class buf_seq_end_opaq);

}  // namespace detail

constexpr auto buffer_sequence_begin = [](auto&& what) {
    using detail::get_buffer_sequence_begin;
    return get_buffer_sequence_begin((decltype(what)&&)(what));
};

constexpr auto buffer_sequence_end = [](auto&& what) {
    using detail::get_buffer_sequence_end;
    return get_buffer_sequence_end((decltype(what)&&)(what));
};

template <typename Seq>
constexpr std::size_t buffer_size(const Seq& seq) {
    auto        iter = buffer_sequence_begin(seq);
    const auto  stop = buffer_sequence_end(seq);
    std::size_t size = 0;
    while (iter != stop) {
        size += static_cast<std::size_t>(iter->size());
        ++iter;
    }
    return size;
}

template <typename MutableSeq, typename ConstSeq>
constexpr std::size_t
buffer_copy(const MutableSeq& dest, const ConstSeq& src, std::size_t max_copy) {
    auto        remaining_to_copy = max_copy;
    std::size_t n_copied          = 0;
    auto        dest_iter         = buffer_sequence_begin(dest);
    const auto  dest_stop         = buffer_sequence_end(dest);
    auto        src_iter          = buffer_sequence_begin(src);
    const auto  src_stop          = buffer_sequence_end(dest);

    std::size_t src_offset  = 0;
    std::size_t dest_offset = 0;

    while (dest_iter != dest_stop && src_iter != src_stop && remaining_to_copy) {
        const_buffer   src_buf  = *src_iter + src_offset;
        mutable_buffer dest_buf = *dest_iter + dest_offset;

        const auto copy_now
            = std::min(src_buf.size(), std::min(dest_buf.size(), remaining_to_copy));
        std::memcpy(dest_buf.data(), src_buf.data(), copy_now);
        n_copied += copy_now;
        src_buf += n_copied;
        dest_buf += n_copied;
        src_offset += n_copied;
        dest_offset += n_copied;
        if (src_buf.size() == 0) {
            ++src_iter;
            src_offset = 0;
        }
        if (dest_buf.size() == 0) {
            ++dest_iter;
            dest_offset = 0;
        }
    }

    return 0;
}

template <typename MutableSeq, typename ConstSeq>
constexpr std::size_t buffer_copy(const MutableSeq& dest, const ConstSeq& src) {
    auto src_size  = buffer_size(src);
    auto dest_size = buffer_size(dest);
    auto min_size  = (src_size > dest_size) ? dest_size : src_size;
    return buffer_copy(dest, src, min_size);
}

}  // namespace neo