--  ls2.adb
--    Purpose: list contents of directory or directories
--    Action:  if no args, use .  else list files in args
--    BUG:     try './ls2 /tmp'

with
  Ada.Command_Line,
  Ada.Integer_Text_IO,
  Ada.Text_IO,
  Interfaces.C.Strings,
  POSIX.C,
  POSIX.Calendar,
  POSIX.File_Status,
  POSIX.Files,
  POSIX.Group_Database,
  POSIX.Process_Identification,
  POSIX.User_Database;

with
  EUP.Text_IO.Permission_IO,
  EUP.Strings;

procedure ls is
   package File_Status        renames POSIX.File_Status;
   package Permission_Text_IO renames EUP.Text_IO.Permission_IO;
   package Process_IDs        renames POSIX.Process_Identification;

   function Mode_To_Letters (Info : in     File_Status.Status) return String is
      Permission_String : constant String :=
        Permission_Text_IO.Image (File_Status.Permission_Set_Of (Info));
   begin
      if File_Status.Is_Directory (File_Status => Info) then
         return 'd' & Permission_String;
      elsif File_Status.Is_Character_Special_File (File_Status => Info) then
         return 'c' & Permission_String;
      elsif File_Status.Is_Block_Special_File (File_Status => Info) then
         return 'b' & Permission_String;
      else
         return '-' & Permission_String;
      end if;
   end Mode_To_Letters;

   function Name_Of (ID : in     Process_IDs.User_ID) return String is
      User : constant POSIX.User_Database.User_Database_Item :=
        POSIX.User_Database.Get_User_Database_Item (ID);
   begin
      return EUP.Strings.Format
        (POSIX.User_Database.User_Name_Of (User), Width => 8);
   exception
      when others =>
         return EUP.Strings.Format
           (Process_IDs.Image (ID), Width => 8);
   end Name_Of;

   function Name_Of (ID : in     Process_IDs.Group_ID)
                    return String is
      Group : constant POSIX.Group_Database.Group_Database_Item :=
        POSIX.Group_Database.Get_Group_Database_Item (ID);
   begin
      return EUP.Strings.Format
        (POSIX.Group_Database.Group_Name_Of (Group), Width => 8);
   exception
      when others =>
         return EUP.Strings.Format
           (Process_IDs.Image (ID), Width => 8);
   end Name_Of;

   procedure Put (Item : in     POSIX.Calendar.POSIX_Time) is
      function ctime (timep : access POSIX.C.time_t)
                     return Interfaces.C.Strings.chars_ptr;
      pragma Import (C, ctime);
      Buffer : aliased POSIX.C.time_t :=
        POSIX.C.time_t (POSIX.Get_Seconds (POSIX.Calendar.To_Timespec (Item)));
   begin
      Ada.Text_IO.Put
        (Interfaces.C.Strings.Value (ctime (Buffer'Access)) (5 .. 16));
   end Put;

   procedure List_File (D_Entry : in     POSIX.Files.Directory_Entry;
                        Quit    : in out Boolean) is
      Name : constant POSIX.POSIX_String := POSIX.Files.Filename_Of (D_Entry);
      Info : File_Status.Status;
   begin
      Info := File_Status.Get_File_Status (Name);

      Ada.Text_IO.Put (Mode_To_Letters (Info));
      Ada.Text_IO.Put (" ");
      Ada.Integer_Text_IO.Put (File_Status.Link_Count_Of (Info), Width => 2);
      Ada.Text_IO.Put (" ");
      Ada.Text_IO.Put (Name_Of (File_Status.Owner_Of (Info)));
      Ada.Text_IO.Put (" ");
      Ada.Text_IO.Put (Name_Of (File_Status.Group_Of (Info)));
      Ada.Text_IO.Put (" ");
      if File_Status.Is_Regular_File (Info) then
         Ada.Integer_Text_IO.Put
           (Item  => Natural (File_Status.Size_Of (Info)),
            Width => 8);
      else
         Ada.Text_IO.Put ("     n/a");
      end if;
      Ada.Text_IO.Put (" ");
      Put (File_Status.Last_Modification_Time_Of (Info));
      Ada.Text_IO.Put (" ");
      Ada.Text_IO.Put_Line (EUP.Strings.To_Ada_String (Name));
   exception
      when others =>
         Ada.Text_IO.Put_Line
           (File => Ada.Text_IO.Standard_Error,
            Item => "Exception in procedure List_File (while " &
              "showing """ & EUP.Strings.To_Ada_String (Name) & """).");
   end List_File;

   procedure List is
     new POSIX.Files.For_Every_Directory_Entry (Action => List_File);
begin
   if Ada.Command_Line.Argument_Count < 1 then
      List (".");
   else
      for Index in 1 .. Ada.Command_Line.Argument_Count loop
         declare
            Directory : String renames Ada.Command_Line.Argument (Index);
         begin
            Ada.Text_IO.Put_Line (Directory & ":");
            List (POSIX.To_POSIX_String (Directory));
         exception
            when others =>
               Ada.Text_IO.Put_Line (File => Ada.Text_IO.Standard_Error,
                                     Item => Ada.Command_Line.Command_Name &
                                       ": cannot open " & Directory);
         end;
      end loop;
   end if;
end ls;

