/*

Copyright (c) 2007, 2011, 2013-2021, Arvid Norberg
Copyright (c) 2016, 2021, Alden Torres
All rights reserved.

You may use, distribute and modify this code under the terms of the BSD license,
see LICENSE file.
*/

#ifndef TORRENT_CHAINED_BUFFER_HPP_INCLUDED
#define TORRENT_CHAINED_BUFFER_HPP_INCLUDED

#include "libtorrent/config.hpp"
#include "libtorrent/aux_/debug.hpp"
#include "libtorrent/aux_/buffer.hpp"

#include <deque>
#include <type_traits>
#include <utility>
#include <vector>

#include "libtorrent/aux_/disable_warnings_push.hpp"
#include <boost/asio/buffer.hpp>
#include "libtorrent/aux_/disable_warnings_pop.hpp"

#ifdef _MSC_VER
// visual studio requires the value in a deque to be copyable. C++11
// has looser requirements depending on which functions are actually used.
#define TORRENT_CPP98_DEQUE 1
#else
#define TORRENT_CPP98_DEQUE 0
#endif

namespace libtorrent::aux {

	// TODO: 2 this type should probably be renamed to send_buffer
	struct TORRENT_EXTRA_EXPORT chained_buffer : private single_threaded
	{
		chained_buffer(): m_bytes(0), m_capacity(0)
		{
			thread_started();
#if TORRENT_USE_ASSERTS
			m_destructed = false;
#endif
		}

	private:

		// destructs/frees the holder object
		using destruct_holder_fun = void (*)(void*);
		using move_construct_holder_fun = void (*)(void*, void*);

		struct buffer_t
		{
			buffer_t() {}
#if TORRENT_CPP98_DEQUE
			buffer_t(buffer_t&& rhs) noexcept
			{
				destruct_holder = rhs.destruct_holder;
				move_holder = rhs.move_holder;
				buf = rhs.buf;
				size = rhs.size;
				used_size = rhs.used_size;
				move_holder(&holder, &rhs.holder);
			}
			buffer_t& operator=(buffer_t&& rhs) & noexcept
			{
				destruct_holder(&holder);
				destruct_holder = rhs.destruct_holder;
				move_holder = rhs.move_holder;
				buf = rhs.buf;
				size = rhs.size;
				used_size = rhs.used_size;
				move_holder(&holder, &rhs.holder);
				return *this;
			}
			buffer_t(buffer_t const& rhs) noexcept
				: buffer_t(std::move(const_cast<buffer_t&>(rhs))) {}
			buffer_t& operator=(buffer_t const& rhs) & noexcept
			{ return this->operator=(std::move(const_cast<buffer_t&>(rhs))); }
#else
			buffer_t(buffer_t&&) = delete;
			buffer_t& operator=(buffer_t&&) = delete;
			buffer_t(buffer_t const&) = delete;
			buffer_t& operator=(buffer_t const&) = delete;
#endif

			destruct_holder_fun destruct_holder;
#if TORRENT_CPP98_DEQUE
			move_construct_holder_fun move_holder;
#endif
			alignas(alignof(std::max_align_t)) std::array<std::uint8_t, 32> holder;

			char* buf = nullptr; // the first byte of the buffer
			int size = 0; // the total size of the buffer
			int used_size = 0; // this is the number of bytes to send/receive
		};

	public:

		bool empty() const { return m_bytes == 0; }
		int size() const { return m_bytes; }
		int capacity() const { return m_capacity; }

		void pop_front(int bytes_to_pop);

		// Holder concept: provides char* data() const. If the holder
		// also has a zero-arg size() (e.g. aux::buffer's malloc-usable
		// rounded capacity, or a span's length), the trailing bytes
		// beyond used_size are exposed as slack for subsequent
		// send_buffer() calls to coalesce small messages into. Holders
		// without size() (e.g. disk_buffer_holder, whose backing
		// allocation may not be fully owned) suppress that piggybacking.
		template <typename Holder>
		void append_buffer(Holder buffer, int used_size)
		{
			TORRENT_ASSERT(is_single_thread());
			m_vec.emplace_back();
			buffer_t& b = m_vec.back();
			init_buffer_entry<Holder>(b, std::move(buffer), used_size);
		}

		template <typename Holder>
		void prepend_buffer(Holder buffer, int used_size)
		{
			TORRENT_ASSERT(is_single_thread());
			m_vec.emplace_front();
			buffer_t& b = m_vec.front();
			init_buffer_entry<Holder>(b, std::move(buffer), used_size);
		}

		// returns the number of bytes available at the
		// end of the last chained buffer.
		int space_in_last_buffer();

		// tries to copy the given buffer to the end of the
		// last chained buffer. If there's not enough room
		// it returns nullptr
		char* append(span<char const> buf);

		// tries to allocate memory from the end
		// of the last buffer. If there isn't
		// enough room, returns 0
		char* allocate_appendix(int s);

		span<boost::asio::const_buffer const> build_iovec(int to_send);

		void clear();

		void build_mutable_iovec(int bytes, std::vector<span<char>>& vec);

		~chained_buffer();

	private:
		template <typename T, typename = void>
		struct has_size : std::false_type
		{};
		template <typename T>
		struct has_size<T, std::void_t<decltype(std::declval<T const&>().size())>> : std::true_type
		{};

		template <typename Holder>
		void init_buffer_entry(buffer_t& b, Holder buf, int used_size)
		{
			static_assert(sizeof(Holder) <= sizeof(b.holder), "buffer holder too large");

			b.buf = buf.data();
			if constexpr (has_size<Holder>::value)
				b.size = int(buf.size());
			else
				b.size = used_size;
			TORRENT_ASSERT(b.size >= used_size);
			b.used_size = used_size;

#ifdef _MSC_VER
// this appears to be a false positive msvc warning
#pragma warning(push, 1)
#pragma warning(disable : 4100)
#endif
			b.destruct_holder = [](void* holder)
			{ reinterpret_cast<Holder*>(holder)->~Holder(); };

#if TORRENT_CPP98_DEQUE
			b.move_holder = [](void* dst, void* src)
			{ new (dst) Holder(std::move(*reinterpret_cast<Holder*>(src))); };
#endif

#ifdef _MSC_VER
#pragma warning(pop)
#endif

			new (&b.holder) Holder(std::move(buf));

			m_bytes += used_size;
			TORRENT_ASSERT(m_capacity < (std::numeric_limits<int>::max)() - b.size);
			m_capacity += b.size;
			TORRENT_ASSERT(m_bytes <= m_capacity);
		}

		template <typename Buffer>
		void build_vec(int bytes, std::vector<Buffer>& vec);

		// this is the list of all the buffers we want to
		// send
		std::deque<buffer_t> m_vec;

		// this is the number of bytes in the send buf.
		// this will always be equal to the sum of the
		// size of all buffers in vec
		int m_bytes;

		// the total size of all buffers in the chain
		// including unused space
		int m_capacity;

		// this is the vector of buffers used when
		// invoking the async write call
		std::vector<boost::asio::const_buffer> m_tmp_vec;

#if TORRENT_USE_ASSERTS
		bool m_destructed;
#endif
	};
}

#endif
