with H2.OS;
with H2.Ascii;

generic
	type Slim_Character is (<>);
	type Wide_Character is (<>);
	type Slim_String is array(System_Index range<>) of Slim_Character;
	type Wide_String is array(System_Index range<>) of Wide_Character;
	with function Slim_To_Wide (Slim: in Slim_String) return Wide_String;
	with function Wide_To_Slim (Wide: in Wide_String) return Slim_String;
	with function Sequence_Length (Slim: in Slim_Character) return System_Length;

package H2.IO is

	package OS is new H2.OS (Slim_Character, Wide_Character, Slim_String, Wide_String, Slim_To_Wide, Wide_To_Slim);
	package Ascii is new H2.Ascii (Slim_Character, Wide_Character);

	package File is

		subtype Flag_Record is OS.File.Flag_Record;
		subtype Flag_Bits is OS.File.Flag_Bits;

		FLAG_READ:       constant Flag_Bits := OS.File.FLAG_READ;
		FLAG_WRITE:      constant Flag_Bits := OS.File.FLAG_WRITE;
		FLAG_CREATE:     constant Flag_Bits := OS.File.FLAG_CREATE;
		FLAG_EXCLUSIVE:  constant Flag_Bits := OS.File.FLAG_EXCLUSIVE;
		FLAG_TRUNCATE:   constant Flag_Bits := OS.File.FLAG_TRUNCATE;
		FLAG_APPEND:     constant Flag_Bits := OS.File.FLAG_APPEND;
		FLAG_NONBLOCK:   constant Flag_Bits := OS.File.FLAG_NONBLOCK;
		FLAG_SYNC:       constant Flag_Bits := OS.File.FLAG_SYNC;
		FLAG_NOFOLLOW:   constant Flag_Bits := OS.File.FLAG_NOFOLLOW;

		type Option_Bits is new System_Word;
		type Option_Record is record
			Bits: Option_Bits := 0;
			LF: System_Byte := Ascii.Code.LF;
			CR: System_Byte := Ascii.Code.CR;
		end record;

		-- Convert LF to CR/LF in Put_Line
		OPTION_CRLF_IN:  constant Option_Bits := 2#0000_0000_0000_0001#;
		OPTION_CRLF_OUT: constant Option_Bits := 2#0000_0000_0000_0010#;

		type File_Buffer is private;
		type File_Record is limited private;
			
		procedure Set_Flag_Bits (Flag: in out Flag_Record; 
		                         Bits: in     Flag_Bits) renames OS.File.Set_Flag_Bits;

		procedure Clear_Flag_Bits (Flag: in out Flag_Record;
		                           Bits: in     Flag_Bits) renames OS.File.Clear_Flag_Bits;

		procedure Set_Option_Bits (Option: in out Option_Record;
		                           Bits:   in     Option_Bits);

		procedure Clear_Option_Bits (Option: in out Option_Record;
		                             Bits:   in     Option_Bits);
	
		function Is_Open (File: in File_Record) return Standard.Boolean;
		pragma Inline (Is_Open);

		procedure Open (File: in out File_Record; 
		                Name: in     Slim_String;
		                Flag: in     Flag_Record;
		                Pool: in     Storage_Pool_Pointer := null);

		procedure Open (File: in out File_Record;
		                Name: in     Wide_String;
		                Flag: in     Flag_Record;
		                Pool: in     Storage_Pool_Pointer := null);

		procedure Close (File: in out File_Record);

		procedure Set_Option (File: in out File_Record; Option: in Option_Record);

		function Get_Option (File: in File_Record) return Option_Record;

		-- The Read procedure reads as many characters as the buffer 
		-- can hold with a single system call at most.
		procedure Read (File:   in out File_Record; 
		                Buffer: out    Slim_String;
		                Length: out    System_Length);

		-- The Read_Line procedure reads a single line into the bufer.
		-- If the buffer is not large enough, it may not contain a full line. 
		-- The remaining part can be returned in the next call.
		procedure Read_Line (File:   in out File_Record; 
		                     Buffer: out    Slim_String;
		                     Length: out    System_Length);


		-- The Get_Line procedure acts like Read_Line but the line terminator
		-- is translated to LF.
		procedure Get_Line (File:   in out File_Record;
		                    Buffer: out    Slim_String;
		                    Length: out    System_Length);

		procedure Read (File:   in out File_Record;
		                Buffer: out    Wide_String;
		                Length: out    System_Length);

		procedure Read_Line (File:   in out File_Record;
		                     Buffer: out    Wide_String;
		                     Length: out    System_Length);

		procedure Get_Line (File:   in out File_Record;
		                    Buffer: out    Wide_String;
		                    Length: out    System_Length);

		procedure Write (File:   in out File_Record; 
		                 Buffer: in     Slim_String;
		                 Length: out    System_Length);

		-- The Write_Line procedure doesn't add a line terminator.
		-- It writes to the underlying file if the internal buffer
		-- is full or writes up to the last line terminator found.
		procedure Write_Line (File:   in out File_Record;
		                      Buffer: in     Slim_String;
		                      Length: out    System_Length);

		procedure Put_Line (File:   in out File_Record;
		                    Buffer: in     Slim_String;
		                    Length: out    System_Length);


		procedure Write (File:   in out File_Record;
		                 Buffer: in     Wide_String;
		                 Length: out    System_Length);

		procedure Write_Line (File:   in out File_Record;
		                      Buffer: in     Wide_String;
		                      Length: out    System_Length);

		procedure Put_Line (File:   in out File_Record;
		                    Buffer: in     Wide_String;
		                    Length: out    System_Length);

		procedure Flush (File: in out File_Record);
		procedure Drain (File: in out File_Record);

		--procedure Rewind (File: in out File_Record);
		--procedure Set_Position (File: in out File_Record; Position: Position_Record);
		--procedure Get_Position (File: in out File_Record; Position: Position_Record);

	private
		type File_Buffer is record
			-- TODO: determine the best buffer size.
			-- The Data array size must be as large as the longest 
			-- multi-byte sequence for a single wide character.
			Data: System_Byte_Array (1 .. 2048); 
			Pos: System_Length := 0;
			Last: System_Length := 0;
		end record;

		type File_Record is limited record
			File: OS.File.File_Pointer := null;
			Option: Option_Record;
			Rbuf: File_Buffer;
			Wbuf: File_Buffer;
			EOF: Standard.Boolean := false;
		end record;

	end File;

end H2.IO;

