-------------------------------------------------------------------------------
-- (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 Command_Line;
with Files;
with SPARK.Ada.Command_Line.Unbounded_String;
with SPARK.Ada.Command_Line;
with SPARK.Ada.Strings.Unbounded;
with SPARK.Ada.Text_IO.Unbounded_String;
with SPARK.Ada.Text_IO;
with Version;

with GNAT.Directory_Operations;
with GNAT.OS_Lib;

use type Command_Line.Status_T;

--# inherit Command_Line,
--#         Files,
--#         SPARK.Ada.Command_Line,
--#         SPARK.Ada.Command_Line.Unbounded_String,
--#         SPARK.Ada.Strings.Unbounded,
--#         SPARK.Ada.Text_IO,
--#         SPARK.Ada.Text_IO.Unbounded_String,
--#         Version;

--  This is the main program for SPARK Clean.
--
--  We have one hidden procedure, Do_Clean, that does as little as
--  possible: it traverses the current directory tree and calls
--  Should_Clean on each file. If this function returns true then the
--  file is deleted.
--
--  The rest is fully written in SPARK. Overall we do the following:
--
--  1. Parse command-line
--  2. Print help message if requested or if an error was found in the
--     command line.
--  3. Traverse current directory tree and delete requested files. If
--     no specific file type has been requested (sparkclean has been
--     invoked without arguments) then all files we know about will
--     be deleted.
--  4. Set the return code to zero or non-zero as appropriate.

--# main_program;
procedure SPARKClean
--# global in out SPARK.Ada.Command_Line.State;
--#        in out SPARK.Ada.Text_IO.The_Standard_Error;
--#        in out SPARK.Ada.Text_IO.The_Standard_Output;
is
   CL        : Command_Line.Data_T;
   CL_Status : Command_Line.Status_T;

   Overall_Success : Boolean;

   --  This function attempts to work out a file's extension. It
   --  should do something like this:
   --     "foobar.vcg"    -> "vcg"
   --     ".vcg"          -> "vcg"
   --     "foobar"        -> ""
   --     "fail."         -> ""
   --     "foo.bar.baz"   -> "baz"
   function Get_Extension (File_Name : in String) return SPARK.Ada.Strings.Unbounded.Unbounded_String is
      Ext           : SPARK.Ada.Strings.Unbounded.Unbounded_String;
      Ext_Starts_At : Natural;
   begin
      --  Search for the last '.' in the filename.
      Ext_Starts_At := 0;
      --# check File_Name'First > Ext_Starts_At;
      for N in reverse Natural range File_Name'Range loop
         --# assert N in File_Name'Range;
         if File_Name (N) = '.' then
            Ext_Starts_At := N;
            exit;
         end if;
      end loop;

      --  Copy and return the extension, if it exists.
      Ext := SPARK.Ada.Strings.Unbounded.Get_Null_Unbounded_String;
      if Ext_Starts_At >= 1 and Ext_Starts_At < File_Name'Last then
         for N in Positive range Ext_Starts_At + 1 .. File_Name'Last loop
            --# assert N in File_Name'Range
            --#   and SPARK.Ada.Strings.Unbounded.Get_Length (Ext) < N;
            SPARK.Ada.Strings.Unbounded.Append_Char (Ext, File_Name (N));
         end loop;
      end if;
      return Ext;
   end Get_Extension;

   --  Using the CL (command-line data), this function checks if the
   --  given file should be deleted based on its extension.
   function Should_Clean (File_Name : in String) return Boolean
   --# global in CL;
   --#        in CL_Status;
   --# pre CL_Status = Command_Line.Ok;
   is
      File_Type : Files.File_Types;
      Result    : Boolean;
   begin
      --# accept F, 30, CL_Status, "This is only needed for partial correctness." &
      --#        F, 50, CL_Status, "This is only needed in the precondition";
      File_Type := Files.Filetype_From_Extension (Get_Extension (File_Name));
      case File_Type is
         when Files.Unknown =>
            Result := False;
         when others =>
            Result := CL.Files_To_Delete (File_Type);
      end case;
      return Result;
   end Should_Clean;

   --  This is a hidden procedure which will go through all files in
   --  the current directory and all subdirectories. For each file it
   --  will call Should_Clean above to decide if the file should be
   --  deleted.
   procedure Do_Clean
   --# global in     CL;
   --#        in     CL_Status;
   --#        in out Overall_Success;
   --# pre CL_Status = Command_Line.Ok and
   --#   Overall_Success;
   is
      --# hide Do_Clean;

      Common_Prefix : constant GNAT.Directory_Operations.Dir_Name_Str := GNAT.Directory_Operations.Get_Current_Dir;

      procedure Scan_Directory (Dir : in GNAT.Directory_Operations.Dir_Name_Str) is
         D       : GNAT.Directory_Operations.Dir_Type;
         Str     : String (1 .. 4096);
         Last    : Natural;
         Success : Boolean;
      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);
            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;
                  else
                     --  Recurse
                     Scan_Directory (F & GNAT.OS_Lib.Directory_Separator);
                  end if;
               elsif Should_Clean (F) then
                  GNAT.OS_Lib.Delete_File (F, Success);
                  if not Success then
                     SPARK.Ada.Text_IO.Put_Error ("Failed to delete file ");
                     SPARK.Ada.Text_IO.Put_Line_Error (F (Common_Prefix'Last + 1 .. F'Last));

                     --  Flag that some error has occurred.
                     Overall_Success := False;
                  else
                     SPARK.Ada.Text_IO.Put_Output ("Deleted: ");
                     SPARK.Ada.Text_IO.Put_Line_Output (F (Common_Prefix'Last + 1 .. F'Last));
                  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
      Scan_Directory (Common_Prefix);
   end Do_Clean;

begin
   --  Parse command line arguments.
   Command_Line.Initialize (CL, CL_Status);
   Overall_Success := CL_Status /= Command_Line.Error;

   --  Do what is requested.
   case CL_Status is
      when Command_Line.Ok =>
         --  Clean files.
         Do_Clean;

         --  It is possible that we have failed to delete a file.
         if not Overall_Success then
            SPARK.Ada.Text_IO.Put_Line_Error ("Error: One or more files could not be deleted.");
            SPARK.Ada.Text_IO.Put_Line_Error ("       Re-running sparkclean should tell you which.");
         end if;

      when Command_Line.Help | Command_Line.Error =>
         --  Print usage.
         SPARK.Ada.Text_IO.Put_Output ("SPARKClean ");
         SPARK.Ada.Text_IO.Put_Line_Output (Version.Toolset_Banner_Line);
         SPARK.Ada.Text_IO.Put_Output ("Usage: ");
         SPARK.Ada.Text_IO.Unbounded_String.Put_Output (SPARK.Ada.Command_Line.Unbounded_String.Command_Name);
         SPARK.Ada.Text_IO.Put_Line_Output (" [OPTION]...");
         SPARK.Ada.Text_IO.Put_Line_Output ("Automatically delete files generated by the SPARK tools.");
         SPARK.Ada.Text_IO.Put_Line_Output ("");
         SPARK.Ada.Text_IO.Put_Line_Output ("   -examiner     delete VCs, DPCs and reports/listings");
         SPARK.Ada.Text_IO.Put_Line_Output ("   -simplifier   delete simplified VCs and DPCs");
         SPARK.Ada.Text_IO.Put_Line_Output ("   -victor       delete victor proof logs");
         SPARK.Ada.Text_IO.Put_Line_Output ("   -pogs         delete pogs summary files");
         SPARK.Ada.Text_IO.Put_Line_Output ("");
         SPARK.Ada.Text_IO.Put_Line_Output ("With no OPTION given, all files generated by the SPARK tools will");
         SPARK.Ada.Text_IO.Put_Line_Output ("be deleted.");
         SPARK.Ada.Text_IO.Put_Line_Output ("");
         SPARK.Ada.Text_IO.Put_Line_Output (Version.Toolset_Support_Line1);
         SPARK.Ada.Text_IO.Put_Line_Output (Version.Toolset_Support_Line2);
         SPARK.Ada.Text_IO.Put_Line_Output (Version.Toolset_Support_Line3);
         SPARK.Ada.Text_IO.Put_Line_Output (Version.Toolset_Support_Line4);
   end case;

   --  Set exit status.
   if Overall_Success then
      SPARK.Ada.Command_Line.Set_Exit_Status (SPARK.Ada.Command_Line.Success);
   else
      SPARK.Ada.Command_Line.Set_Exit_Status (SPARK.Ada.Command_Line.Failure);
   end if;
end SPARKClean;
