-------------------------------------------------------------------------------
-- (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.
--
--=============================================================================

--------------------------------------------------------------------------------
--Synopsis:                                                                   --
--                                                                            --
--Procedure to analyse a .SPD file                                            --
--                                                                            --
--------------------------------------------------------------------------------

separate (VCS)
procedure Analyse_Summary_DP_File
  (Report_File        : in     SPARK_IO.File_Type;
   Filename           : in     E_Strings.T;
   DPC_File_Date_Time : in     E_Strings.T;
   Error_In_SDP_File  :    out Boolean)
is

   Bad_File_Format                       : Boolean            := False;
   Dummy_Close_Status                    : SPARK_IO.File_Status;
   File_Line                             : E_Strings.T;
   Finished_With_File                    : Boolean;
   Open_Status                           : SPARK_IO.File_Status;
   Process_Success                       : Boolean;
   Read_Line_Success                     : Boolean;
   Summary_DP_File                       : SPARK_IO.File_Type := SPARK_IO.Null_File;
   File_Status                           : File_Status_T;
   Summary_Date_Time                     : E_Strings.T;
   DP_Generation_Date_Time_From_SDP_File : E_Strings.T;
   Trimmed_Line                          : E_Strings.T;
   Current_DPC_Name                      : E_Strings.T;

   -------------------------------------------------------------------------
   -- NOTE, this procedure also removes the comma inserted in the string
   -- by the simplifier
   procedure Extract_Dates_And_Times_From_Summary_DPC_File
     (Summary_DP_File          : in     SPARK_IO.File_Type;
      DPC_Generation_Date_Time :    out E_Strings.T;
      SDP_Date_Time            :    out E_Strings.T;
      File_Status              :    out File_Status_T)
   --# global in out SPARK_IO.File_Sys;
   --# derives DPC_Generation_Date_Time,
   --#         File_Status,
   --#         SDP_Date_Time,
   --#         SPARK_IO.File_Sys        from SPARK_IO.File_Sys,
   --#                                       Summary_DP_File;
   is
      File_Line        : E_Strings.T;
      Trimmed_Line     : E_Strings.T;
      Gen_Date_Time    : E_Strings.T;
      Simp_Date_Time   : E_Strings.T;
      Subprogram_Found : Boolean := False;
   begin
      File_Status              := Not_Corrupt;
      DPC_Generation_Date_Time := E_Strings.Empty_String;
      SDP_Date_Time            := E_Strings.Empty_String;

      --Check for completly empty file.
      E_Strings.Get_Line (File  => Summary_DP_File,
                          E_Str => File_Line);
      if E_Strings.Eq1_String (E_Str => File_Line,
                               Str   => "") and SPARK_IO.End_Of_File (Summary_DP_File) then
         File_Status := Corrupt_Empty_File;
      else
         --Keep on reading from this file, until the desired information is retrieved
         --or the end of the file is reached.
         loop
            Trimmed_Line := E_Strings.Trim (File_Line);

            -- find date
            -- (There is an implicit assumption that the date, if present, will appear
            --  before the subprogram name.)
            -- When the Examiner is in plain_output mode, the DATE line doesn't appear.
            if E_Strings.Eq1_String (E_Str => E_Strings.Section (Trimmed_Line, 1, 7),
                                     Str   => "CREATED") then
               -- extract the VC generation date and time from the string
               Gen_Date_Time :=
                 E_Strings.Section (Trimmed_Line, SDP_File_VC_Generation_Date_Start_Column, SDP_File_VC_Generation_Date_Length);
               E_Strings.Append_String (E_Str => Gen_Date_Time,
                                        Str   => " ");
               E_Strings.Append_Examiner_String
                 (E_Str1 => Gen_Date_Time,
                  E_Str2 => E_Strings.Section
                    (Trimmed_Line,
                     SDP_File_VC_Generation_Time_Start_Column,
                     SDP_File_VC_Generation_Time_Length));
               DPC_Generation_Date_Time := Gen_Date_Time;

               -- extract the simplification date and time from the string
               Simp_Date_Time :=
                 E_Strings.Section (Trimmed_Line, SDP_File_Simplification_Date_Start_Column, SDP_File_Simplification_Date_Length);
               E_Strings.Append_String (E_Str => Simp_Date_Time,
                                        Str   => " ");
               E_Strings.Append_Examiner_String
                 (E_Str1 => Simp_Date_Time,
                  E_Str2 => E_Strings.Section
                    (Trimmed_Line,
                     SDP_File_Simplification_Time_Start_Column,
                     SDP_File_Simplification_Time_Length));

               SDP_Date_Time := Simp_Date_Time;
            end if;

            Subprogram_Found := Is_Valid_Subprogram (Trimmed_Line);

            exit when (Subprogram_Found or SPARK_IO.End_Of_File (Summary_DP_File));
            E_Strings.Get_Line (File  => Summary_DP_File,
                                E_Str => File_Line);
         end loop;
      end if;

      if (File_Status = Not_Corrupt) and not Subprogram_Found then
         File_Status := Corrupt_Unknown_Subprogram;
      end if;

      -- if date has not been found must be in plain output mode
      -- The above is a false assumption -- the file may just be corrupt. However, the
      -- effect below of setting the string as unknown date is reasonable for both cases.
      if E_Strings.Eq_String (DPC_Generation_Date_Time, E_Strings.Empty_String) then
         E_Strings.Append_String (E_Str => DPC_Generation_Date_Time,
                                  Str   => Unknown_DPC_Date);
         E_Strings.Append_String (E_Str => SDP_Date_Time,
                                  Str   => Unknown_SDP_Date);
      end if;
   end Extract_Dates_And_Times_From_Summary_DPC_File;

   -------------------------------------------------------------------------
   -- look at the next non-blank line to see whether it starts
   -- "*** true". If so the VC has been discharged. Otherwise, increment
   -- the counter of undischarged VCs, and set the flag that an undischarged
   -- VC has been found
   procedure Process_New_Summary_DP_Line
     (Summary_DP_File : in     SPARK_IO.File_Type;
      DPC_Name        : in     E_Strings.T;
      Success         :    out Boolean)
   --# global in out FatalErrors.State;
   --#        in out SPARK_IO.File_Sys;
   --#        in out VCHeap.State;
   --# derives FatalErrors.State,
   --#         VCHeap.State      from *,
   --#                                DPC_Name,
   --#                                SPARK_IO.File_Sys,
   --#                                Summary_DP_File,
   --#                                VCHeap.State &
   --#         SPARK_IO.File_Sys,
   --#         Success           from SPARK_IO.File_Sys,
   --#                                Summary_DP_File;
   is
      File_Line         : E_Strings.T;
      Read_Line_Success : Boolean;
   begin
      Read_Next_Non_Blank_Line (File      => Summary_DP_File,
                                Success   => Read_Line_Success,
                                File_Line => File_Line);
      if not Read_Line_Success then
         Success := False;
      else
         Success := True;
         if E_Strings.Eq1_String (E_Str => E_Strings.Section (File_Line, 1, 11),
                                  Str   => "*** No dead") then
            VCHeap.Set_DPC_State (DPC_Name, VCDetails.DPC_Live);
         elsif E_Strings.Eq1_String (E_Str => E_Strings.Section (File_Line, 1, 8),
                                     Str   => "*** Dead") then
            VCHeap.Set_DPC_State (DPC_Name, VCDetails.DPC_Dead);
         elsif E_Strings.Eq1_String
           -- Case when Examiner determined that no dead path search
           -- is required for this DPC.
           (E_Str => E_Strings.Section (File_Line, 1, 7),
            Str   => "*** DPC") then
            VCHeap.Set_DPC_State (DPC_Name, VCDetails.DPC_Unchecked);
         else
            Success := False;
         end if;
      end if;
   end Process_New_Summary_DP_Line;

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

begin -- Analyse_Summary_DPC_File

   -- open SDP file
   E_Strings.Open
     (File         => Summary_DP_File,
      Mode_Of_File => SPARK_IO.In_File,
      Name_Of_File => Filename,
      Form_Of_File => "",
      Status       => Open_Status);
   if Open_Status /= SPARK_IO.Ok then
      FatalErrors.Process (FatalErrors.Could_Not_Open_Input_File, E_Strings.Empty_String);
   end if;

   --No errors, until discover otherwise.
   Error_In_SDP_File := False;

   Extract_Dates_And_Times_From_Summary_DPC_File
     (Summary_DP_File          => Summary_DP_File,
      DPC_Generation_Date_Time => DP_Generation_Date_Time_From_SDP_File,
      SDP_Date_Time            => Summary_Date_Time,
      File_Status              => File_Status);
   case File_Status is
      when Not_Corrupt =>
         null;
      when Corrupt_Empty_File =>
         SPARK_IO.Put_Line (SPARK_IO.Standard_Output, "************* SDP file corrupt: empty file ************", 0);
         SPARK_IO.New_Line (SPARK_IO.Standard_Output, 1);
         Error_In_SDP_File := True;
      when Corrupt_Unknown_Subprogram =>
         SPARK_IO.Put_Line (SPARK_IO.Standard_Output, "************* SDP file corrupt: missing subprogram name ************", 0);
         SPARK_IO.New_Line (SPARK_IO.Standard_Output, 1);
         Error_In_SDP_File := True;
   end case;

   --Only continue working on this file if an error has not been seen.
   --(Previously POGS would attempt to work with corrupt files. This feature has the
   -- capacity to produce confusing and wrong results.)
   if not (Error_In_SDP_File) then
      if CommandLine.Data.IgnoreDates
        or else E_Strings.Eq_String (E_Str1 => DP_Generation_Date_Time_From_SDP_File,
                                     E_Str2 => DPC_File_Date_Time) then

         if not CommandLine.Data.IgnoreDates then
            SPARK_IO.New_Line (Report_File, 1);
            SPARK_IO.Put_String (Report_File, "DPC ZombieScoped ", 0);
            E_Strings.Put_Line (File  => Report_File,
                                E_Str => Summary_Date_Time);
         end if;

         -- find first non blank line
         -- if we get to the end of the file first, flag a fatal error
         Read_Next_Non_Blank_Line (File      => Summary_DP_File,
                                   Success   => Read_Line_Success,
                                   File_Line => File_Line);

         if not Read_Line_Success then
            SPARK_IO.Put_Line (SPARK_IO.Standard_Output, "************* SDP file corrupt: no data beyond header ************", 0);
            SPARK_IO.New_Line (SPARK_IO.Standard_Output, 1);
            Bad_File_Format := True;
         else
            Finished_With_File := False;

            -- process file line-by-line
            -- on entry to the loop there is already a valid line in the
            -- FileLine buffer
            while not Finished_With_File loop
               -- examine line and act accordingly
               if Is_New_Range_Line (Line => File_Line) then
                  Append_Next_Line_From_File (Line => File_Line,
                                              File => Summary_DP_File);
               elsif Is_New_VC_Line (Line => File_Line) then

                  Trimmed_Line     := E_Strings.Trim (File_Line);
                  Current_DPC_Name :=
                    E_Strings.Section
                    (E_Str     => Trimmed_Line,
                     Start_Pos => 1,
                     Length    => E_Strings.Get_Length (E_Str => Trimmed_Line) - 1);

                  Process_New_Summary_DP_Line
                    (Summary_DP_File => Summary_DP_File,
                     DPC_Name        => Current_DPC_Name,
                     Success         => Process_Success);

                  if not Process_Success then
                     SPARK_IO.Put_String (Report_File, "*** Warning: Bad format in summary DP file ***", 0);
                     Finished_With_File := True;
                     Bad_File_Format    := True;
                  end if;
               end if;

               if not Finished_With_File then
                  -- read next line
                  Read_Next_Non_Blank_Line (File      => Summary_DP_File,
                                            Success   => Read_Line_Success,
                                            File_Line => File_Line);

                  -- if unsuccessful then check EOF
                  -- and set FinishedWithFile accordingly
                  if not Read_Line_Success then
                     if SPARK_IO.End_Of_File (Summary_DP_File) then
                        Finished_With_File := True;
                     else
                        FatalErrors.Process (FatalErrors.Problem_Reading_File, E_Strings.Empty_String);
                     end if;
                  end if;
               end if;
            end loop;

         end if;

      else
         -- SDP file is out of date
         SPARK_IO.New_Line (Report_File, 1);
         SPARK_IO.Put_Line (Report_File, "*** Warning: Summary DP file out of date ***", 0);
         SPARK_IO.Put_String (Report_File, "DPCs Generated: ", 0);
         E_Strings.Put_String (File  => Report_File,
                               E_Str => DPC_File_Date_Time);
         SPARK_IO.New_Line (Report_File, 1);

         SPARK_IO.Put_String (Report_File, "SDP File Date: ", 0);
         E_Strings.Put_String (File  => Report_File,
                               E_Str => DP_Generation_Date_Time_From_SDP_File);
         SPARK_IO.New_Line (Report_File, 1);

         Bad_File_Format := True;
      end if;
   end if;

   --# accept F, 10, Dummy_Close_Status, "DummyCloseStatus unused here" &
   --#        F, 10, Summary_DP_File, "Summary_DP_File unused here";
   SPARK_IO.Close (Summary_DP_File, Dummy_Close_Status);
   --# end accept;

   --Either an error being raised, or a 'BadFileFormat'
   --being detected is an error.
   Error_In_SDP_File := Error_In_SDP_File or Bad_File_Format;

   --# accept F, 33, Dummy_Close_Status, "DummyCloseStatus unused here";
end Analyse_Summary_DP_File;
