with Ada.Containers.Vectors;
with Ada.Command_Line; use Ada.Command_Line;
with Ada.IO_Exceptions;
with Ada.Text_IO;      use Ada.Text_IO;

with GNATCOLL.Mmap;    use GNATCOLL.Mmap;

procedure Driver is
   Next_Arg : Positive := 1;

   function Get_Next_Arg return String;
   --  Return the next argument on the command line, or return an empty string
   --  if there is no argument left.

   ------------------
   -- Get_Next_Arg --
   ------------------

   function Get_Next_Arg return String is
   begin
      if Next_Arg > Argument_Count then
         return "";
      else
         Next_Arg := Next_Arg + 1;
         return Argument (Next_Arg - 1);
      end if;
   end Get_Next_Arg;

   type Region_Record is record
      Offset, Length : File_Size;
      Region         : Mapped_Region;
   end record;

   package Region_Vectors is new Ada.Containers.Vectors
     (Index_Type   => Positive,
      Element_Type => Region_Record);
   use Region_Vectors;

   type Mode_Type is (Read, Write);
   type Method_Type is (Mmap, Buffer);
   type Action_Type is (Map, Remap, Fill);

   Filename         : constant String := Get_Next_Arg;
   Mode             : constant Mode_Type := Mode_Type'Value (Get_Next_Arg);
   Method           : constant Method_Type := Method_Type'Value (Get_Next_Arg);
   Close_After_Read : constant Boolean := Get_Next_Arg = "close-after-read";

   File     : Mapped_File;
   Regions  : Vector;
   Action   : Action_Type;
   I        : Positive := 1;
begin
   begin
      case Mode is
         when Read =>
            File := Open_Read
              (Filename, Use_Mmap_If_Available => Method = Mmap);
         when Write =>
            File := Open_Write
              (Filename, Use_Mmap_If_Available => Method = Mmap);
      end case;
   exception
      when Ada.IO_Exceptions.Name_Error =>
         Put_Line ("Open: Name_Error");
         return;
   end;

   --  Parse arguments to get the regions to map

   loop
      declare
         Next_Arg : constant String := Get_Next_Arg;
      begin
         --  If there is no argument left, stop parsing arguments

         if Next_Arg = "" then
            exit;
         else
            Action := Action_Type'Value (Next_Arg);
         end if;
      end;

      case Action is
         when Map | Remap =>
            declare
               Offset  : constant File_Size := File_Size'Value (Get_Next_Arg);
               Length  : constant File_Size := File_Size'Value (Get_Next_Arg);
               Mutable : constant Boolean := Boolean'Value (Get_Next_Arg);
            begin
               --  If this is a new region, append it to existing regions.
               --  Otherwise, remap the last added region.

               if Action = Map then
                  Regions.Append
                    ((Offset, Length, Read (File, Offset, Length, Mutable)));

               else
                  declare
                     Region : Region_Record renames Regions (Regions.Last);
                  begin
                     Read (File, Region.Region, Offset, Length, Mutable);
                     Region.Offset := Offset;
                     Region.Length := Length;
                  end;
               end if;
            end;

         when Fill =>
            declare
               Region     : Mapped_Region renames
                 Regions (Regions.Last).Region;
               R_Data     : constant Str_Access := Data (Region);

               Offset     : constant Integer := Integer'Value (Get_Next_Arg);
               Length     : constant Integer := Integer'Value (Get_Next_Arg);
               Filler     : constant String := Get_Next_Arg;
               Filler_Cur : Integer := Filler'First;
            begin
               for I in Offset .. Offset + Length - 1 loop
                  R_Data (I) := Filler (Filler_Cur);
                  if Filler_Cur = Filler'Last then
                     Filler_Cur := Filler'First;
                  else
                     Filler_Cur := Filler_Cur + 1;
                  end if;
               end loop;
            end;
      end case;
   end loop;

   if Mode = Write then
      --  Free and re-read all regions so that the bufferized changes are
      --  propagated to the actual file.

      for Region of Regions loop
         Free (Region.Region);
         Region.Region := Read (File, Region.Offset, Region.Length);
      end loop;
   end if;

   if Close_After_Read then
      --  Closing a file twice is allowed and should do nothing

      Close (File);
      Close (File);
   end if;

   --  Dump each region so that the testsuite can check the library did its
   --  work.

   for Region of Regions loop
      Put_Line
        ("==" & Positive'Image (I) & " - " & Filename
         & ": region from" & File_Size'Image (Offset (Region.Region))
         & " to" & File_Size'Image
            (Offset (Region.Region) + File_Size (Last (Region.Region)))
         & " ==");
      Put_Line ("Size:" & Integer'Image (Last (Region.Region)));
      if Last (Region.Region) >= 1 then
         --  Is_Mutable on empty regions has undefined result: do not put any
         --  expectation on it.

         Put_Line ("Mutable: " & Boolean'Image (Is_Mutable (Region.Region)));
      end if;
      Put_Line (String (Data (Region.Region).all) (1 .. Last (Region.Region)));
      Put_Line ("");
      I := I + 1;
   end loop;

   for Region of Regions loop
      --  Freeing a region twice is allowed and should do nothing

      Free (Region.Region);
      Free (Region.Region);
   end loop;

   if not Close_After_Read then
      --  Closing a file twice is allowed and should no nothing

      Close (File);
      Close (File);
   end if;
end Driver;
