-------------------------------------------------------------------------------
-- (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 .VCG file                                            --
--                                                                            --
--------------------------------------------------------------------------------

separate (VCS)
procedure AnalyseVCFile
  (Report_File    : in     SPARK_IO.File_Type;
   Filename       : in     E_Strings.T;
   Error_In_File  :    out Boolean;
   File_Date_Time :    out E_Strings.T)
is

   Dummy_Close_Status : SPARK_IO.File_Status;
   The_Date_Time      : E_Strings.T;
   File_Line          : E_Strings.T;
   Finished_With_File : Boolean;
   Open_Status        : SPARK_IO.File_Status;
   Read_Line_Success  : Boolean;
   Sub_Program_Name   : E_Strings.T;
   VC_File            : SPARK_IO.File_Type := SPARK_IO.Null_File;
   VC_Info            : VC_Info_Type;
   File_Status        : File_Status_T;
   Current_VC_Name    : E_Strings.T;
   Parsing_State      : Parsing_State_Type := Initial;
   Trimmed_Line       : E_Strings.T;

   ------------------------------------------------------------------------
   procedure Extract_VC_File_Date_Time_And_Subprog_Name
     (VC_File          : in     SPARK_IO.File_Type;
      File_Date_Time   :    out E_Strings.T;
      Sub_Program_Name :    out E_Strings.T;
      File_Status      :    out File_Status_T)
   --# global in out SPARK_IO.File_Sys;
   --# derives File_Date_Time,
   --#         File_Status,
   --#         SPARK_IO.File_Sys,
   --#         Sub_Program_Name  from SPARK_IO.File_Sys,
   --#                                VC_File;
   is
      File_Line        : E_Strings.T;
      Trimmed_Line     : E_Strings.T;
      Subprogram_Found : Boolean := False;
   begin
      File_Status      := Not_Corrupt;
      File_Date_Time   := E_Strings.Empty_String;
      Sub_Program_Name := E_Strings.Empty_String;

      -- Check for completely empty file.
      E_Strings.Get_Line (File  => VC_File,
                          E_Str => File_Line);
      if E_Strings.Is_Empty (E_Str => File_Line) and SPARK_IO.End_Of_File (VC_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.)
            if E_Strings.Eq1_String (E_Str => E_Strings.Section (Trimmed_Line, 1, 4),
                                     Str   => "DATE") then
               File_Date_Time := E_Strings.Section (Trimmed_Line, VCG_File_Date_Time_Start_Column, VCG_File_Date_Time_Length);
            end if;

            Subprogram_Found := Is_Valid_Subprogram (Trimmed_Line);
            if Subprogram_Found then
               --  Note that this does not actually work if we have a
               --  wrapped line. The original code for
               --  Is_Valid_Subprogram will cater for wrapped line
               --  that, but we seem to not care about it here.
               Sub_Program_Name := E_Strings.Trim (File_Line);
            end if;

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

      if E_Strings.Is_Empty (E_Str => File_Date_Time) then
         File_Date_Time := E_Strings.Copy_String (Str => "Unknown Date (for vc generation)");
      end if;

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

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

   function Get_Line_Number (Line_Number : VC_Line_Type) return E_Strings.T is
      Number         : Integer;
      Trimmed_Result : E_Strings.T;
   begin
      if Line_Number = Refinement_Or_Inheritance_VC then
         Trimmed_Result := E_Strings.Copy_String (Str => "     ");
      elsif Line_Number = VC_Line_Start then
         Trimmed_Result := E_Strings.Copy_String (Str => "start");
      elsif Line_Number = VC_Line_End then
         Trimmed_Result := E_Strings.Copy_String (Str => "finish");
      else
         Number := Line_Number;
         E_Strings.Put_Int_To_String (Dest     => Trimmed_Result,
                                      Item     => Number,
                                      Start_Pt => 1,
                                      Base     => 10);
         Trimmed_Result := E_Strings.Trim (E_Str => Trimmed_Result);
      end if;

      return Trimmed_Result;
   end Get_Line_Number;

begin -- AnalyseVCFile
   Current_VC_Name := E_Strings.Empty_String;

   -- open VC file
   E_Strings.Open
     (File         => VC_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_File := False;

   Extract_VC_File_Date_Time_And_Subprog_Name
     (VC_File          => VC_File,
      File_Date_Time   => The_Date_Time,
      Sub_Program_Name => Sub_Program_Name,
      File_Status      => File_Status);

   --  Report any error to standard out and set error flag
   --  accordingly.
   case File_Status is
      when Not_Corrupt =>
         null;
      when Corrupt_Empty_File =>
         SPARK_IO.Put_Line (SPARK_IO.Standard_Output, "************* VC file corrupt: empty file ************", 0);
         SPARK_IO.New_Line (SPARK_IO.Standard_Output, 1);
         Error_In_File := True;
      when Corrupt_Unknown_Subprogram =>
         SPARK_IO.Put_Line (SPARK_IO.Standard_Output, "************* VC file corrupt: missing subprogram name ************", 0);
         SPARK_IO.New_Line (SPARK_IO.Standard_Output, 1);
         Error_In_File := True;
   end case;

   --Record the date regardless of errors. This may be a string of the form 'no date'.
   File_Date_Time := The_Date_Time;

   --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_File then

      SPARK_IO.Put_String (Report_File, "File ", 0);
      if CommandLine.Data.PlainOutput then
         E_Strings.Put_Line
           (File  => Report_File,
            E_Str => E_Strings.Lower_Case (E_Str => OSFiling.Base_Filename (Path => Filename)));
      else
         E_Strings.Put_Line (File  => Report_File,
                             E_Str => Filename);
      end if;

      E_Strings.Put_Line (File  => Report_File,
                          E_Str => Sub_Program_Name);
      SPARK_IO.New_Line (Report_File, 1);

      if CommandLine.Data.IgnoreDates then
         SPARK_IO.Put_Line (Report_File, "*** Warning: VC date stamps ignored ***", 0);
      else
         SPARK_IO.Put_String (Report_File, "VCs generated ", 0);
         E_Strings.Put_Line (File  => Report_File,
                             E_Str => The_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      => VC_File,
                                Success   => Read_Line_Success,
                                File_Line => File_Line);

      if not Read_Line_Success then
         SPARK_IO.Put_Line (SPARK_IO.Standard_Output, "************* VC file corrupt: no data beyond header ************", 0);
         SPARK_IO.New_Line (SPARK_IO.Standard_Output, 1);
         Error_In_File := True;
      else
         -- if it's an error message then reproduce this and exit
         -- assumption: that if an error message is present it will be
         -- the first non blank line after the file header
         if Is_VC_Error_Message (Line => File_Line) then
            SPARK_IO.New_Line (Report_File, 1);
            E_Strings.Put_String (File  => Report_File,
                                  E_Str => File_Line);
            Error_In_File := True;
         else
            Error_In_File := False;

            -- initialize the 'current information' structure
            VC_Info :=
              VC_Info_Type'
              (Start_Line              => VC_Line_Start,
               End_Line                => VC_Line_End,
               End_Line_Point_Type     => VCDetails.Undetermined_Point,
               Number_Of_VCs           => 0,
               This_Start_Line_Printed => False,
               File_Type               => Standard_VC_File_Type,
               Any_VCs_Printed         => False,
               Valid                   => False);

            Finished_With_File := False;

            -- process file line-by-line
            -- on entry to the loop there is already a valid line in the
            -- File_Line buffer
            while not Finished_With_File loop
               -- examine line and act accordingly
               if Is_New_Range_Line (Line => File_Line) then
                  case Parsing_State is
                     when Initial =>
                        Parsing_State := First_Range;
                     when First_VC_Name =>
                        Parsing_State := New_Range;
                     when New_VC_Name =>
                        Parsing_State := New_Range;
                     when others =>
                        null;
                  end case;

                  Append_Next_Line_From_File (Line => File_Line,
                                              File => VC_File);

                  ProcessNewRangeLine (File_Line, VC_Info);

               elsif Is_New_VC_Line (Line => File_Line) then
                  case Parsing_State is
                     when First_Range =>
                        -- Initialise VCHeap and store the first VC on the VCHeap
                        Trimmed_Line    := E_Strings.Trim (File_Line);
                        Current_VC_Name :=
                          E_Strings.Section
                          (E_Str     => Trimmed_Line,
                           Start_Pos => 1,
                           Length    => E_Strings.Get_Length (E_Str => Trimmed_Line) - 1);

                        Parsing_State := First_VC_Name;
                        VCHeap.Reinitialize
                          (Current_VC_Name,
                           Get_Line_Number (Line_Number => VC_Info.Start_Line),
                           Get_Line_Number (Line_Number => VC_Info.End_Line),
                           VC_Info.End_Line_Point_Type);
                        VCHeap.Set_VC_State (Current_VC_Name, VCDetails.VC_SIV_Not_Present);
                     when First_VC_Name =>
                        Trimmed_Line    := E_Strings.Trim (File_Line);
                        Current_VC_Name :=
                          E_Strings.Section
                          (E_Str     => Trimmed_Line,
                           Start_Pos => 1,
                           Length    => E_Strings.Get_Length (E_Str => Trimmed_Line) - 1);
                        Parsing_State   := New_VC_Name;
                        VCHeap.Add
                          (VCHeap.First_Entry,
                           Current_VC_Name,
                           Get_Line_Number (Line_Number => VC_Info.Start_Line),
                           Get_Line_Number (Line_Number => VC_Info.End_Line),
                           VC_Info.End_Line_Point_Type,
                           VCDetails.VC_SIV_Not_Present,
                           VCDetails.DPC_Not_Present);
                     when New_Range =>
                        -- Store a new VC on the VC Heap
                        Trimmed_Line    := E_Strings.Trim (File_Line);
                        Current_VC_Name :=
                          E_Strings.Section
                          (E_Str     => Trimmed_Line,
                           Start_Pos => 1,
                           Length    => E_Strings.Get_Length (E_Str => Trimmed_Line) - 1);
                        Parsing_State   := New_VC_Name;
                        VCHeap.Add
                          (VCHeap.First_Entry,
                           Current_VC_Name,
                           Get_Line_Number (Line_Number => VC_Info.Start_Line),
                           Get_Line_Number (Line_Number => VC_Info.End_Line),
                           VC_Info.End_Line_Point_Type,
                           VCDetails.VC_SIV_Not_Present,
                           VCDetails.DPC_Not_Present);
                     when New_VC_Name =>
                        -- The range has not changed, but store a new VC on the VC Heap
                        Trimmed_Line    := E_Strings.Trim (File_Line);
                        Current_VC_Name :=
                          E_Strings.Section
                          (E_Str     => Trimmed_Line,
                           Start_Pos => 1,
                           Length    => E_Strings.Get_Length (E_Str => Trimmed_Line) - 1);
                        Parsing_State   := New_VC_Name;
                        VCHeap.Add
                          (VCHeap.First_Entry,
                           Current_VC_Name,
                           Get_Line_Number (Line_Number => VC_Info.Start_Line),
                           Get_Line_Number (Line_Number => VC_Info.End_Line),
                           VC_Info.End_Line_Point_Type,
                           VCDetails.VC_SIV_Not_Present,
                           VCDetails.DPC_Not_Present);
                     when others =>
                        null;
                  end case;

                  VC_Info.Number_Of_VCs := VC_Info.Number_Of_VCs + 1;

               elsif Is_Trivially_True_VC (Line => File_Line) then

                  VCHeap.Set_VC_State (Current_VC_Name, VCDetails.VC_Proved_By_Examiner);

               elsif Is_Trivially_False_VC (Line => File_Line) then

                  VCHeap.Set_VC_State (Current_VC_Name, VCDetails.VC_False);

               end if;

               -- read next line
               Read_Next_Non_Blank_Line (File      => VC_File,
                                         Success   => Read_Line_Success,
                                         File_Line => File_Line);

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

            -- write information for last VC
            -- two VC_Info parameters are necessary as WriteVC_Info compares them
            -- in deciding what to write (see definition of WriteVC_Info)
            if not VC_Info.Valid then
               SPARK_IO.Put_Line (Report_File, "No VCs in file", 0);
            end if;
         end if;
      end if;
   end if;

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

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