module Protocol::HTTP::Body::Stream::Reader

def each(&block)

@yields {|chunk| ...} Each chunk of data.

Iterate over each chunk of data from the input stream.
def each(&block)
	return to_enum unless block_given?
	
	if @buffer
		yield @buffer
		@buffer = nil
	end
	
	while chunk = read_next
		yield chunk
	end
end

def gets(separator = NEWLINE, limit = nil, chomp: false)

@parameter *options [Hash] Additional options, passed to {read_until}.
@parameter limit [Integer] The maximum number of bytes to read.
@parameter separator [String] The line separator, defaults to `\n`.

Read a single line from the stream.
def gets(separator = NEWLINE, limit = nil, chomp: false)
	# If the separator is an integer, it is actually the limit:
	if separator.is_a?(Integer)
		limit = separator
		separator = NEWLINE
	end
	
	# If no separator is given, this is the same as a read operation:
	if separator.nil?
		# I tried using `read(limit)` here but it will block until the limit is reached, which is not usually desirable behaviour.
		return read_partial(limit)
	end
	
	# We don't want to split on the separator, so we subtract the size of the separator:
	split_offset = separator.bytesize - 1
	
	@buffer ||= read_next
	return nil if @buffer.nil?
	
	offset = 0
	until index = @buffer.index(separator, offset)
		offset = @buffer.bytesize - split_offset
		offset = 0 if offset < 0
		
		# If we have gone past the limit, we are done:
		if limit and offset >= limit
			@buffer.freeze
			matched = @buffer.byteslice(0, limit)
			@buffer = @buffer.byteslice(limit, @buffer.bytesize)
			return matched
		end
		
		# Read more data:
		if chunk = read_next
			@buffer << chunk
		else
			# No more data could be read, return the remaining data:
			buffer = @buffer
			@buffer = nil
			
			return @buffer
		end
	end
	
	# Freeze the buffer, as this enables us to use byteslice without generating a hidden copy:
	@buffer.freeze
	
	if limit and index > limit
		line = @buffer.byteslice(0, limit)
		@buffer = @buffer.byteslice(limit, @buffer.bytesize)
	else
		line = @buffer.byteslice(0, index+(chomp ? 0 : separator.bytesize))
		@buffer = @buffer.byteslice(index+separator.bytesize, @buffer.bytesize)
	end
	
	return line
end

def read(length = nil, buffer = nil)

@returns [String] a buffer containing the data
@parameter buffer [String] the buffer which will receive the data
@parameterlength [Integer] the amount of data to read

If buffer is given, then the read data will be placed into buffer instead of a newly created String object.

If the length is not given, it will read all data until EOF, or return an empty string if the stream is already at EOF.

If given a non-negative length, it will read at most that many bytes from the stream. If the stream is at EOF, it will return nil.

Read data from the underlying stream.
def read(length = nil, buffer = nil)
	return "" if length == 0
	
	buffer ||= String.new.force_encoding(Encoding::BINARY)
	
	# Take any previously buffered data and replace it into the given buffer.
	if @buffer
		buffer.replace(@buffer)
		@buffer = nil
	else
		buffer.clear
	end
	
	if length
		while buffer.bytesize < length and chunk = read_next
			buffer << chunk
		end
		
		# This ensures the subsequent `slice!` works correctly.
		buffer.force_encoding(Encoding::BINARY)
		
		# This will be at least one copy:
		@buffer = buffer.byteslice(length, buffer.bytesize)
		
		# This should be zero-copy:
		buffer.slice!(length, buffer.bytesize)
		
		if buffer.empty?
			return nil
		else
			return buffer
		end
	else
		while chunk = read_next
			buffer << chunk
		end
		
		return buffer
	end
end

def read_nonblock(length, buffer = nil, exception: nil)

@parameter buffer [String | Nil] The buffer to read into.
@parameter length [Integer] The maximum number of bytes to read.

Read data from the stream without blocking if possible.
def read_nonblock(length, buffer = nil, exception: nil)
	@buffer ||= read_next
	chunk = nil
	
	unless @buffer
		buffer&.clear
		return
	end
	
	if @buffer.bytesize > length
		chunk = @buffer.byteslice(0, length)
		@buffer = @buffer.byteslice(length, @buffer.bytesize)
	else
		chunk = @buffer
		@buffer = nil
	end
	
	if buffer
		buffer.replace(chunk)
	else
		buffer = chunk
	end
	
	return buffer
end

def read_partial(length = nil, buffer = nil)

@parameter length [Integer] The maximum number of bytes to read.

Will avoid reading from the underlying stream if there is buffered data available.

If the length is given, at most length bytes will be read. Otherwise, one chunk of data from the underlying stream will be read.

Read some bytes from the stream.
def read_partial(length = nil, buffer = nil)
	if @buffer
		if buffer
			buffer.replace(@buffer)
		else
			buffer = @buffer
		end
		@buffer = nil
	else
		if chunk = read_next
			if buffer
				buffer.replace(chunk)
			else
				buffer = chunk
			end
		else
			buffer&.clear
			buffer = nil
		end
	end
	
	if buffer and length
		if buffer.bytesize > length
			# This ensures the subsequent `slice!` works correctly.
			buffer.force_encoding(Encoding::BINARY)
			@buffer = buffer.byteslice(length, buffer.bytesize)
			buffer.slice!(length, buffer.bytesize)
		end
	end
	
	return buffer
end

def read_until(pattern, offset = 0, chomp: false)

@returns [String] The contents of the stream up until the pattern, which is consumed but not returned.
@parameter chomp [Boolean] Whether to remove the pattern from the returned data.
@parameter offset [Integer] The offset to start searching from.
@parameter pattern [String] The pattern to match.

Read data from the stream until encountering pattern.
def read_until(pattern, offset = 0, chomp: false)
	# We don't want to split on the pattern, so we subtract the size of the pattern.
	split_offset = pattern.bytesize - 1
	
	@buffer ||= read_next
	return nil if @buffer.nil?
	
	until index = @buffer.index(pattern, offset)
		offset = @buffer.bytesize - split_offset
		
		offset = 0 if offset < 0
		
		if chunk = read_next
			@buffer << chunk
		else
			return nil
		end
	end
	
	@buffer.freeze
	matched = @buffer.byteslice(0, index+(chomp ? 0 : pattern.bytesize))
	@buffer = @buffer.byteslice(index+pattern.bytesize, @buffer.bytesize)
	
	return matched
end

def readpartial(length, buffer = nil)

@parameter buffer [String] The buffer to read into.
@parameter length [Integer] The maximum number of bytes to read.

Similar to {read_partial} but raises an `EOFError` if the stream is at EOF.
def readpartial(length, buffer = nil)
	read_partial(length, buffer) or raise EOFError, "End of file reached!"
end