with Ada.Streams;              use Ada.Streams;
with Ada.Streams.Stream_IO;
with Ada.Unchecked_Conversion;
with GNATCOLL.Mmap;            use GNATCOLL.Mmap;

package body File_IO is

   Buffer_Size : constant Natural := Get_Page_Size * 4;

   function To_Stream_Element_Array
     (Data : String) return Stream_Element_Array
   is

      subtype Fixed_String is String (Data'First .. Data'Last);

      subtype Fixed_Array is Stream_Element_Array
        (Stream_Element_Offset (Data'First)
           .. Stream_Element_Offset (Data'Last));

      function To_Stream_Elements is
         new Ada.Unchecked_Conversion (Fixed_String, Fixed_Array);

   begin
      return To_Stream_Elements (Data);
   end To_Stream_Element_Array;

   function Read_File (File_Name : String) return Unbounded_String
   is
      File  : GNATCOLL.Mmap.Mapped_File;
      Str   : GNATCOLL.Mmap.Str_Access;
      Offs  : GNATCOLL.Mmap.File_Size := 0;
      Chunk : constant GNATCOLL.Mmap.File_Size :=
                GNATCOLL.Mmap.File_Size (Buffer_Size);
      Ret   : Unbounded_String;

   begin
      File := Open_Read (File_Name);

      while Offs < Length (File) loop
         Read (File, Offs, Length => Chunk);
         Str := Data (File);

         Ada.Strings.Unbounded.Append
           (Ret,
            String (Str (Integer (Offs - Offset (File) + 1) .. Last (File))));

         Offs := Offs + GNATCOLL.Mmap.File_Size (Last (File));
      end loop;

      Close (File);

      return Ret;
   end Read_File;

   procedure Write_File (File_Name : String; File_Contents : Unbounded_String)
   is
      use type Ada.Streams.Stream_Element_Offset;
      File  : Ada.Streams.Stream_IO.File_Type;
      Index : Integer := 1;

   begin
      Ada.Streams.Stream_IO.Create
         (File => File,
          Mode => Ada.Streams.Stream_IO.Out_File,
          Name => File_Name);

      Write_Loop :
      loop
         declare
            Slice_Last : Integer;
         begin
            Slice_Last := Index + Integer (Buffer_Size) - 1;
            if Slice_Last > Length (File_Contents) then
               Slice_Last := Length (File_Contents);
            end if;

            declare
               Buffer : constant Ada.Streams.Stream_Element_Array :=
                 To_Stream_Element_Array (Data => Slice (File_Contents,
                                                         Index,
                                                         Slice_Last));
            begin
               Ada.Streams.Stream_IO.Write (File => File, Item => Buffer);
               Index := Slice_Last + 1;
            end;
         end;
         exit Write_Loop when Index > Length (File_Contents);
      end loop Write_Loop;

      Ada.Streams.Stream_IO.Close (File);
   end Write_File;

end File_IO;
