note
	description : "[
			Objects that represents the input stream
		]"
	specification: "EWSGI/connector specification https://github.com/Eiffel-World/Eiffel-Web-Framework/wiki/EWSGI-specification"
	legal: "See notice at end of class."
	status: "See notice at end of class."
	date: "$Date$"
	revision: "$Revision$"

deferred class
	WGI_INPUT_STREAM

inherit
	STRING_HANDLER

feature -- Input

	read_character
			-- Read the next character in input stream.
			-- Make the result available in `last_character'
		require
			is_open_read: is_open_read
			not_end_of_input: not end_of_input
		deferred
		end

	read_string (nb: INTEGER)
			-- Read the next `nb' characters and
			-- make the string result available in `last_string'
		require
			is_open_read: is_open_read
			not_end_of_input: not end_of_input
			nb_large_enough: nb > 0
		deferred
		ensure
			last_string_count_small_enough: not end_of_input implies last_string.count <= nb
			character_read: not end_of_input implies last_string.count > 0
		end

	read_to_string (a_string: STRING; pos, nb: INTEGER): INTEGER
			-- Fill `a_string', starting at position `pos', with
			-- at most `nb' characters read from input stream.
			-- Return the number of characters actually read.
			-- (Note that even if at least `nb' characters are available
			-- in the input stream, there is no guarantee that they
			-- will all be read.)
		require
			is_open_read: is_open_read
			not_end_of_input: not end_of_input
			a_string_not_void: a_string /= Void
			valid_position: pos > 0 and pos <= a_string.capacity
			nb_large_enough: nb > 0
			nb_small_enough: nb <= a_string.capacity - pos + 1
		local
			i, end_pos: INTEGER
		do
			end_pos := pos + nb - 1
			from
				i := pos
			until
				i > end_pos
			loop
				read_character
				if last_character_available then
					a_string.put (last_character, i)
					if end_of_input then
						Result := i
							-- Jump out of the loop.
						i := end_pos + 1
					else
						i := i + 1
					end
				else
						-- reached end of input
						-- Jump out of the loop.
					i := end_pos + 1
				end
			end
			if not end_of_input then
				Result := i
			end
			a_string.set_count (Result)
			Result := Result - pos + 1
		ensure
			nb_char_read_large_enough: Result >= 0
			nb_char_read_small_enough: Result <= nb
			character_read: not end_of_input implies Result > 0
		end

	append_to_string (a_string: STRING; nb: INTEGER)
			-- Append at most `nb' characters read from input stream
			-- to `a_string'
			-- Set `last_appended_count' to the number of characters actually read.
			-- (Note that even if at least `nb' characters are available
			-- in the input stream, there is no guarantee that they
			-- will all be read.)
		require
			is_open_read: is_open_read
			not_end_of_input: not end_of_input
			a_string_not_void: a_string /= Void
			nb_large_enough: nb > 0
		local
			i, end_pos: INTEGER
			l_count: INTEGER
		do
			from
				i := a_string.count + 1
				end_pos := i + nb - 1
				a_string.grow (end_pos)
			until
				i > end_pos
			loop
				read_character
				if last_character_available then
					a_string.extend (last_character)
					l_count := l_count + 1

					if end_of_input then
							-- Jump out of the loop.
						i := end_pos + 1
					else
						i := i + 1
					end
				else
						-- reached end of input
						-- Jump out of the loop.
					i := end_pos + 1
				end
			end
			last_appended_count := l_count
		ensure
			nb_char_read_large_enough: last_appended_count >= 0
			nb_char_read_small_enough: last_appended_count <= nb
			character_read: not end_of_input implies last_appended_count > 0
		end

	append_to_file (a_file: FILE; nb: INTEGER)
			-- Append at most `nb' characters read from input stream
			-- to `a_file'
			-- Set `last_appended_count' to the number of characters actually read.
			-- (Note that even if at least `nb' characters are available
			-- in the input stream, there is no guarantee that they
			-- will all be read.)
		require
			is_open_read: is_open_read
			not_end_of_input: not end_of_input
			a_file_attached: a_file /= Void
			a_file_is_open_write: a_file.is_open_write
			nb_large_enough: nb > 0
		local
			s: like last_string
			n: INTEGER
			l_remaining: INTEGER
		do
			from
				n := nb.min (2_048)
				l_remaining := nb - n
			until
				l_remaining = 0 or n = 0
			loop
				read_string (n)
				s := last_string
				a_file.put_string (s)
				if end_of_input or s.count < n then
					n := s.count
						-- no more data
					l_remaining := l_remaining - n
					n := 0
				else
					n := s.count
					l_remaining := l_remaining - n
				end
			end
			last_appended_count := nb - l_remaining
				-- Clean `last_string'
			last_string.wipe_out
		ensure
			nb_char_read_large_enough: last_appended_count >= 0
			nb_char_read_small_enough: last_appended_count <= nb
			character_read: not end_of_input implies last_appended_count > 0
		end

feature -- Access

	last_string: STRING_8
			-- Last string read.
			--
			-- Note: this query *might* return the same object.
			-- Therefore a clone should be used if the result
			-- is to be kept beyond the next call to this feature.
			-- However `last_string' is not shared between file objects.)
		require
			is_open_read: is_open_read
			not_end_of_input: not end_of_input
		deferred
		ensure
			last_string_not_void: Result /= Void
		end

	last_character: CHARACTER_8
			-- Last item read.
		require
			is_open_read: is_open_read
			not_end_of_input: not end_of_input
		deferred
		end

	last_character_available: BOOLEAN
			-- Is `last_character' available? i.e read?
			--| with chunked encoding, we may reach the end and the `last_character'
			--| should not be used.
			--| to redefine in descendant if needed
		do
			Result := True
		end

	last_appended_count: INTEGER
			-- Count of characters actually read by last `append_to_string' call.

feature -- Status report

	is_open_read: BOOLEAN
			-- Can items be read from input stream?
		deferred
		end

	end_of_input: BOOLEAN
			-- Has the end of input stream been reached?
		require
			is_open_read: is_open_read
		deferred
		end

note
	copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
	license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
	source: "[
			Eiffel Software
			5949 Hollister Ave., Goleta, CA 93117 USA
			Telephone 805-685-1006, Fax 805-685-6869
			Website http://www.eiffel.com
			Customer support http://support.eiffel.com
		]"
end
