-- This file is  free  software, which  comes  along  with  SmartEiffel. This
-- software  is  distributed  in the hope that it will be useful, but WITHOUT
-- ANY  WARRANTY;  without  even  the  implied warranty of MERCHANTABILITY or
-- FITNESS  FOR A PARTICULAR PURPOSE. You can modify it as you want, provided
-- this header is kept unaltered, and a notification of the changes is added.
-- You  are  allowed  to  redistribute  it and sell it, alone or as a part of
-- another product.
--       Copyright (C) 1994-2002 LORIA - INRIA - U.H.P. Nancy 1 - FRANCE
--          Dominique COLNET and Suzanne COLLIN - SmartEiffel@loria.fr
--                       http://SmartEiffel.loria.fr
--
class TEXT_FILE_READ
   --
   -- Basic input facilities to read a named file on the disc.
   --
   -- Note: most features are common with STD_INPUT so you can test your
   --       program on the screen first and then, just changing of
   --       instance (STD_INPUT/TEXT_FILE_READ), doing the same in a file.
   --

inherit INPUT_STREAM

creation make, connect_to

feature

   path: STRING
         -- Not Void when connected to the corresponding file on the disk.

   is_connected: BOOLEAN is
      do
         Result := path /= Void
      ensure
	 definition: Result = (path /= Void)
      end

   connect_to(new_path: STRING) is
	 --  Open text file for reading. The stream is positioned at the
	 --  beginning of the file.
      require
         not is_connected
         not new_path.is_empty
      local
         p: POINTER
      do
         p := new_path.to_external
         input_stream := basic_io_text_file_read_open(p)
         if input_stream.is_not_null then
            push_back_flag := false
	    end_of_input := False
            path := new_path
	    if capacity = 0 then
	       buffer := buffer.calloc(4096)
	       capacity := 4096
	    end
	    end_reached := False
	    buffer_position := 0
	    buffer_size := 0
         end
      end

   disconnect is
      require
         is_connected
      do
         basic_io_fclose(input_stream)
         path := Void
      end

   read_character is 
      do 
	 push_back_flag := False
	 if buffer_position >= buffer_size then 
	    fill_buffer
	 end 
	 last_character := buffer.item(buffer_position)
	 buffer_position := buffer_position + 1 
	 end_of_input := end_reached
      end

   unread_character is
      do
         push_back_flag := True
	 end_of_input := False
	 buffer_position := buffer_position - 1
      end

   last_character: CHARACTER 

   end_of_input: BOOLEAN

   push_back_flag: BOOLEAN

   read_line_in(str: STRING) is
      local
         i: INTEGER
	 stop: BOOLEAN
	 old_count, new_count: INTEGER
	 initial_count: INTEGER      
      do
	 from
	    initial_count := str.count
	 until
	    stop
	 loop
	    -- search %N in buffer
	    from
	       i := buffer_position
	    until
	       i >= buffer_size or else buffer.item(i) = '%N'
	    loop
	       i := i + 1
	    end

	    -- block copy (but copy_slice copies char by char...)
	    if i > buffer_position then
	       old_count := str.count
	       new_count := old_count + i - buffer_position
	       if str.capacity < new_count then
		  str.resize((old_count*2).max(new_count))
	       end
	       str.storage.copy_slice(old_count, buffer, buffer_position, i-1)
	       str.set_count(new_count)
	    end

	    -- next buffer if needed
	    if i < buffer_size and then buffer.item(i) = '%N' then
	       stop := True
	       buffer_position := i + 1
	       if str.count > initial_count and then str.last = '%R' then
		  str.remove_last(1)
		  -- UNIX uses the Linefeed character (ASCII character 10) to 
		  -- denote the end of a line. DOS uses the Carriage Return 
		  -- followed by the Linefeed character (ASCII character 13 
		  -- & ASCII character 10) to denote a new line.
	       end
	    else
	       if not end_reached then
		  fill_buffer
	       end
	       stop := end_reached
	    end
	 end
	 push_back_flag := False
	 end_of_input := end_reached
      end

feature {FILE_TOOLS}

   same_as(other: like Current): BOOLEAN is
      require
         is_connected
         other.is_connected
      local
	 b1, b2: NATIVE_ARRAY[CHARACTER]
	 i: INTEGER
      do
         from
	    fill_buffer
	    b1 := buffer
	    other.fill_buffer
	    b2 := other.buffer
	    Result := True
         until
	    not Result or else
	    end_reached or else other.end_reached
         loop
	    if buffer_size = other.buffer_size then
	       if b1.item(0) /= b2.item(0) then
		  Result := False
	       else
		  -- make first character different for loop end
		  b1.put('%R', 0)
		  b2.put('%N', 0)
		  from
		     i := buffer_size - 1
		  variant
		     i
		  until
		     b1.item(i) /= b2.item(i)
		  loop
		     i := i - 1
		  end
		  Result := i = 0
	       end
	       if Result then
		  fill_buffer
		  other.fill_buffer
	       end
	    else
	       from
		  read_character
		  other.read_character
	       until
		  not Result or else
		  end_of_input or else other.end_of_input
	       loop
		  Result := last_character = other.last_character
		  read_character
		  other.read_character
	       end
	    end
	 end
	 Result := Result and then end_reached
	    and then other.end_reached
         disconnect
         other.disconnect
      ensure
         not is_connected
         not other.is_connected
      end

feature {INPUT_STREAM}

   input_stream: POINTER

feature {TEXT_FILE_READ} 
   buffer: NATIVE_ARRAY[CHARACTER]
   end_reached: BOOLEAN
   buffer_position, buffer_size: INTEGER
   capacity: INTEGER

   fill_buffer is
      local
	 last: CHARACTER
      do 
	 if buffer_size > 0 then
	    last := buffer.item(buffer_size - 1)
	 end
         buffer_size := basic_io_fread(buffer, capacity, input_stream)

         buffer_position := 0
	 if buffer_size <= 0 then
	    end_reached := True
	    buffer.put(last, 0) -- needed for unread_character service
	    --if buffer_size = -1 => exception ?
	    buffer_size := 1
	    buffer_position := 1
	 end
      end
  
feature {NONE}

   make is
	 -- The new created object is not connected. (See also `connect_to'.)
      do
      ensure
	 not is_connected
      end

   basic_io_text_file_read_open(path_pointer: POINTER): POINTER is
      external "SmartEiffel"
      end

   basic_io_fclose(stream_pointer : POINTER) is
      external "SmartEiffel"
      end

end -- TEXT_FILE_READ