with Byte_Defs;
with Io_Exceptions;
with Managed_Pool_Generic;

generic
    type File_Type is private;
    with procedure Put (Into : File_Type; Data : Byte_Defs.Byte_String) is <>;
    with procedure Get (From : File_Type;
                        Data : out Byte_Defs.Byte_String) is <>;
    -- Get must raise Io_Exceptions.End_Error when there is no more to get.

    Block_Size : Natural := 1024; -- try to Put or Get this many bytes at once.
    -- Any value greater than 2 will work.  Larger values increase memory
    -- utilization, but may improve speed.

package Record_Stream_Generic is

    -- This package provides algorithms for marking record boundaries
    -- within a file of bytes.  The meaning of 'file' is defined by
    -- the user, as generic formal File_Type, together with operations
    -- to read or write it.  A file could be any sequential device that
    -- can store or transfer bytes, for example, a Device_Independent_Io
    -- file, or a Transport connection.

    -- A 'record' is a sequence of consecutive bytes within a file.
    -- Records may be arbitrarily long, and may contain any data pattern.
    -- A record is marked within the file by prefacing each fragment of
    -- it with a two-byte count, indicating how long it is.  See the
    -- package body for more detail.  This scheme introduces overhead:
    -- the number of bytes in the file will be more than the sum of the
    -- number of bytes in its records, owing to the bytes used to store
    -- record lengths.

    -- The underlying file must signal end-of-file by raising End_Error.

    -- This implementation of record marking tries to minimize overhead
    -- and maximize speed using write-behind and read-ahead.  This requires
    -- buffering, which is allocated by Open and freed by Close (below).

    type Id is private;
    -- Identifies the resource (buffer) needed to read or write records.
    -- If two Ids are equal, they identify the same resource.

    type Mode_Type is (Put_Only, Get_Only);

    procedure Open (Stream : out Id; Mode : Mode_Type; File : File_Type);
    -- Prepare to Put (Get) records into (from) the specified File.

    procedure Close (Stream : Id);
    -- Does an implicit Put/Get_End_Of_record if required.

    subtype Byte_String is Byte_Defs.Byte_String;

    procedure Put (Stream : Id; Data : Byte_String);
    -- Append the Data to the end of the current Put record.

    procedure Get (Stream : Id; Data : out Byte_String);
    -- Return the next Data'length bytes from the current Get record.
    -- If there aren't that many bytes in the record, raise End_Error.

    procedure Get (Stream : Id; Data : out Byte_String; Count : out Natural);
    -- Return up to Data'length bytes from the current Get record.
    -- Return the number of bytes (<= Data'length) in Count.

    -- If you Get up to the end of a record, Get will keep on
    -- raising End_Error or returning Count = 0 until you call
    -- Get_End_Of_Record.

    procedure Put_End_Of_Record (Stream : Id);
    -- Conclude the current Put record, and flush the buffer.
    -- The next Put will start a new record.

    procedure Get_End_Of_Record (Stream : Id);
    -- Scan forward to the end of the current Get record.
    -- The next Get will start a new record.
    -- If End_Of_File follows the current Get record,
    -- raise End_Error.

    function End_Of_Record (Stream : Id) return Boolean;
    -- Returns true if we're at the end of the current record.

    Mode_Error : exception renames Io_Exceptions.Mode_Error;
    -- Put to Get_Only, or vice-versa.

    End_Error : exception renames Io_Exceptions.End_Error;
    -- Tried to Get past end of record.

private

    subtype Block_Count is Natural range 0 .. 2 ** 16 - 1;

    Buffer_Size : constant Natural := Block_Size - 2;
    -- a full buffer plus a 2-byte count = a full block.
    subtype Buffer_Count is Natural range 0 .. Buffer_Size;
    subtype Buffer_Index is Natural range 0 .. Buffer_Count'Last - 1;
    subtype Buffer_Array is Byte_Defs.Byte_String (Buffer_Index);

    type State_Type is
        record
            Mode : Mode_Type;
            File : File_Type;
            Total : Long_Integer; -- bytes moved (a statistic).
            Recnum : Natural;     -- record sequence number (a statistic).
            Block : Block_Count;  -- Get: bytes left in the current block.
                                  -- Put: bytes put in previous block.
            Count : Buffer_Count; -- bytes in Buffer.
            First : Buffer_Index; -- index of first byte in Buffer.
            Buffer : Buffer_Array;
        end record;

    procedure Noop (State : in out State_Type);

    package Pool is new Managed_Pool_Generic (State_Type, Noop, Noop, Noop);

    type Id is new Pool.Object_Id;

end Record_Stream_Generic;