-------------------------------------------------------------------------------
-- (C) Altran Praxis Limited
-------------------------------------------------------------------------------
--
-- The SPARK toolset is free software; you can redistribute it and/or modify it
-- under terms of the GNU General Public License as published by the Free
-- Software Foundation; either version 3, or (at your option) any later
-- version. The SPARK toolset is distributed in the hope that it will be
-- useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
-- Public License for more details. You should have received a copy of the GNU
-- General Public License distributed with the SPARK toolset; see file
-- COPYING3. If not, go to http://www.gnu.org/licenses for a complete copy of
-- the license.
--
--=============================================================================

with E_Strings.Not_SPARK;
with GNAT.Directory_Operations;
with GNAT.OS_Lib;
with SystemErrors;

package body Directory_Operations is

   function Prefix
     (E_Str             : E_Strings.T;
      Separator         : Character;
      Include_Separator : Boolean)
     return              E_Strings.T
   -- Returns the text up to the last occurrance of the separator.
   -- If Include_Separator is true then the string returned ends in the separator
   -- otherwise it does not.
   is
      Result : E_Strings.T := E_Strings.Empty_String;
      Last   : Integer     := 0;
   begin
      for I in reverse Integer range 1 .. E_Strings.Get_Length (E_Str => E_Str) loop
         if I /= E_Strings.Get_Length (E_Str => E_Str)
           and then E_Strings.Get_Element (E_Str => E_Str,
                                           Pos   => I) = Separator then
            Last := I;
            exit;
         end if;
      end loop;

      if Last /= 0 then
         if Include_Separator then
            Result := E_Strings.Section (E_Str     => E_Str,
                                         Start_Pos => 1,
                                         Length    => Last);
         else
            Result := E_Strings.Section (E_Str     => E_Str,
                                         Start_Pos => 1,
                                         Length    => Last - 1);
         end if;
      end if;
      return Result;
   end Prefix;

   --------------------------------------------------------------------------------

   function Directory_Separator return Character is
   begin
      return GNAT.OS_Lib.Directory_Separator;
   end Directory_Separator;

   --------------------------------------------------------------------------------

   function Current_Directory return  E_Strings.T is
   begin
      return E_Strings.Copy_String (Str => GNAT.Directory_Operations.Get_Current_Dir);
   end Current_Directory;

   --------------------------------------------------------------------------------

   function Find_Files
     (Matching     : RegularExpression.Object;
      In_Directory : E_Strings.T;
      Recursively  : Boolean)
     return         StringList.Object
   is
      The_Result : StringList.Object;

      procedure Scan_Directory (Dir : in GNAT.Directory_Operations.Dir_Name_Str) is
         D    : GNAT.Directory_Operations.Dir_Type;
         Str  : String (1 .. 1024);
         Last : Natural;
      begin
         GNAT.Directory_Operations.Open (D, Dir);
         loop
            GNAT.Directory_Operations.Read (D, Str, Last);
            exit when Last = 0;

            declare
               F     : constant String := Dir & Str (1 .. Last);
               E_Str : E_Strings.T;
            begin
               if GNAT.OS_Lib.Is_Directory (F) then

                  --  Ignore "." and ".."
                  if ((Last = 1) and then (Str (1) = '.')) or ((Last = 2) and then (Str (1) = '.' and Str (2) = '.')) then
                     null;
                  elsif Recursively then
                     --  Recurse here
                     Scan_Directory (F & GNAT.OS_Lib.Directory_Separator);
                  end if;
               else
                  -- Does this file match the regular expression?
                  E_Str := E_Strings.Copy_String (Str => F);
                  if RegularExpression.Matches (E_Str       => E_Str,
                                                The_Reg_Exp => Matching) then
                     StringList.Add_In_Lex_Order (To_List  => The_Result,
                                                  The_Item => E_Str);
                  end if;
               end if;
            end;

         end loop;
         GNAT.Directory_Operations.Close (D);
      exception
         when others =>
            GNAT.Directory_Operations.Close (D);
            raise;
      end Scan_Directory;

   begin
      The_Result := StringList.Null_Object;
      Scan_Directory (Dir => E_Strings.Not_SPARK.Get_String (E_Str => In_Directory));
      return The_Result;
   end Find_Files;

   --------------------------------------------------------------------------------

   function File_Extension (Path : E_Strings.T) return E_Strings.T is
   begin
      return E_Strings.Copy_String
        (Str => GNAT.Directory_Operations.File_Extension (Path => E_Strings.Not_SPARK.Get_String (E_Str => Path)));
   end File_Extension;

   --------------------------------------------------------------------------------

   procedure Set_Extension (Path : in out E_Strings.T;
                            Ext  : in     E_Strings.T) is
   begin
      if not E_Strings.Is_Empty (E_Str => File_Extension (Path => Path)) then
         -- Has an extension so remove it
         Path := Prefix (E_Str             => Path,
                         Separator         => '.',
                         Include_Separator => False);
      end if;
      -- Add the given extension.
      E_Strings.Append_String (E_Str => Path,
                               Str   => ".");
      E_Strings.Append_Examiner_String (E_Str1 => Path,
                                        E_Str2 => Ext);
   end Set_Extension;

   --------------------------------------------------------------------------------

   function Filename (Path : E_Strings.T) return E_Strings.T is
   begin
      return E_Strings.Copy_String
        (Str => GNAT.Directory_Operations.File_Name (Path => E_Strings.Not_SPARK.Get_String (E_Str => Path)));
   end Filename;

   --------------------------------------------------------------------------------

   function Is_Directory (Path : E_Strings.T) return Boolean is
   begin
      return GNAT.OS_Lib.Is_Directory (Name => E_Strings.Not_SPARK.Get_String (E_Str => Path));
   end Is_Directory;

   --------------------------------------------------------------------------------

   function Is_File (Path : E_Strings.T) return Boolean is
   begin
      return GNAT.OS_Lib.Is_Regular_File (Name => E_Strings.Not_SPARK.Get_String (E_Str => Path));
   end Is_File;

   --------------------------------------------------------------------------------

   function Normalize_Path_Name (Name      : E_Strings.T;
                                 Directory : E_Strings.T) return E_Strings.T is
   begin
      return E_Strings.Copy_String
        (Str => GNAT.OS_Lib.Normalize_Pathname
           (Name          => E_Strings.Not_SPARK.Get_String (E_Str => Name),
            Directory     => E_Strings.Not_SPARK.Get_String (E_Str => Directory),
            Resolve_Links => False));
   end Normalize_Path_Name;

   --------------------------------------------------------------------------------

   procedure Normalise_Dir (D : in out E_Strings.T) is
   begin
      if not Is_Directory (Path => D) then
         SystemErrors.Fatal_Error (Sys_Err => SystemErrors.Other_Internal_Error,
                                   Msg     => "Internal error.");
      elsif E_Strings.Get_Element (E_Str => D,
                                   Pos   => E_Strings.Get_Length (E_Str => D)) /= Directory_Separator then
         E_Strings.Append_Char (E_Str => D,
                                Ch    => Directory_Separator);
      end if;
   end Normalise_Dir;

   --------------------------------------------------------------------------------

   function Up_Dir (D : E_Strings.T) return E_Strings.T is
      Result : E_Strings.T := E_Strings.Empty_String;
   begin
      if not Is_Directory (Path => D) then
         SystemErrors.Fatal_Error (Sys_Err => SystemErrors.Other_Internal_Error,
                                   Msg     => "Internal error.");
      else
         Result := Prefix (E_Str             => D,
                           Separator         => Directory_Separator,
                           Include_Separator => True);
      end if;
      return Result;
   end Up_Dir;

   --------------------------------------------------------------------------------

   function Relative_Name (Of_This_File_Or_Dir : E_Strings.T;
                           To_This_Dir         : E_Strings.T) return E_Strings.T is
      The_Common_Bit : E_Strings.T;
      Of_This        : E_Strings.T;
      To_This        : E_Strings.T;
      Result         : E_Strings.T;

      function Common_Path (P1, P2 : E_Strings.T) return E_Strings.T is
         --
         -- Returns the common directory.
         -- The last char is always the directory separator
         Result : E_Strings.T := E_Strings.Empty_String;
      begin
         for I in Integer range 1 .. E_Strings.Get_Length (E_Str => P1) loop
            exit when I > E_Strings.Get_Length (E_Str => P2);
            exit when E_Strings.Get_Element (E_Str => P1,
                                             Pos   => I) /= E_Strings.Get_Element (E_Str => P2,
                                                                                   Pos   => I);
            E_Strings.Append_Char (E_Str => Result,
                                   Ch    => E_Strings.Get_Element (E_Str => P1,
                                                                   Pos   => I));
         end loop;
         if E_Strings.Get_Element (E_Str => Result,
                                   Pos   => E_Strings.Get_Length (E_Str => Result)) /= Directory_Separator then
            Result := Prefix (E_Str             => Result,
                              Separator         => Directory_Separator,
                              Include_Separator => True);
         end if;
         return Result;
      end Common_Path;

   begin
      Result  := E_Strings.Empty_String;
      To_This := To_This_Dir;
      Of_This := Of_This_File_Or_Dir;

      -- Check the input parameters make sense.
      if Is_File (Path => Of_This) and not Is_Directory (Path => Of_This) then
         Of_This := Prefix (E_Str             => Of_This,
                            Separator         => Directory_Separator,
                            Include_Separator => True);
      end if;

      if Is_Directory (Path => To_This) then
         Normalise_Dir (D => To_This);
      end if;

      if not Is_Directory (Path => Of_This) or not Is_Directory (Path => To_This) then
         SystemErrors.Fatal_Error (Sys_Err => SystemErrors.Other_Internal_Error,
                                   Msg     => "Internal error.");
      end if;

      The_Common_Bit := Common_Path (P1 => Of_This,
                                     P2 => To_This);

      if E_Strings.Is_Empty (E_Str => The_Common_Bit) then
         Result := Of_This_File_Or_Dir;

      elsif E_Strings.Eq_String (E_Str1 => The_Common_Bit,
                                 E_Str2 => To_This) then
         Result :=
           E_Strings.Section
           (E_Str     => Of_This_File_Or_Dir,
            Start_Pos => E_Strings.Get_Length (E_Str => The_Common_Bit) + 1,
            Length    => E_Strings.Get_Length (E_Str => Of_This_File_Or_Dir) - E_Strings.Get_Length (E_Str => The_Common_Bit));

      else
         loop
            To_This := Up_Dir (D => To_This);
            E_Strings.Append_String (E_Str => Result,
                                     Str   => "..");
            E_Strings.Append_Char (E_Str => Result,
                                   Ch    => Directory_Separator);
            exit when E_Strings.Eq_String (E_Str1 => To_This,
                                           E_Str2 => The_Common_Bit);
         end loop;
         E_Strings.Append_Examiner_String
           (E_Str1 => Result,
            E_Str2 => E_Strings.Section
              (E_Str     => Of_This_File_Or_Dir,
               Start_Pos => E_Strings.Get_Length (E_Str => The_Common_Bit) + 1,
               Length    => E_Strings.Get_Length (E_Str => Of_This_File_Or_Dir) -
                 E_Strings.Get_Length (E_Str => The_Common_Bit)));
      end if;
      return Result;
   end Relative_Name;

end Directory_Operations;
