-------------------------------------------------------------------------------
-- (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 .DPC file                                            --
-- At this time, the DPC file has the same format as a VCG file. Most of the  --
-- code has been copied across but the format may change over time and thus   --
-- justification of having a separate subprogram to analyse a DPC file.       --
--------------------------------------------------------------------------------

separate (VCS)
procedure Analyse_DPC_File
  (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;
   Open_Status        : SPARK_IO.File_Status;
   File_Line          : E_Strings.T;
   Read_Line_Success  : Boolean;
   DPC_File           : SPARK_IO.File_Type := SPARK_IO.Null_File;
   File_Status        : File_Status_T;
   VC_Info            : VC_Info_Type;
   Current_DPC_Name   : E_Strings.T;
   Parsing_State      : Parsing_State_Type := Initial;
   Trimmed_Line       : E_Strings.T;
   Finished_With_File : Boolean;

   procedure Extract_DPC_File_Date_Time
     (DPC_File       : in     SPARK_IO.File_Type;
      File_Date_Time :    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 from DPC_File,
   --#                                SPARK_IO.File_Sys;
   is
      File_Line         : E_Strings.T;
      Trimmed_Line      : E_Strings.T;
      Sub_Program_Found : Boolean;
   begin
      File_Status    := Not_Corrupt;
      File_Date_Time := E_Strings.Empty_String;

      -- Check for completely empty file.
      E_Strings.Get_Line (File  => DPC_File,
                          E_Str => File_Line);
      if E_Strings.Eq1_String (E_Str => File_Line,
                               Str   => "") and SPARK_IO.End_Of_File (DPC_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 no need to find details of each dpc as an entry
            -- on the vcheap for each dpc (vc) has already been created.

            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, DPC_File_Date_Time_Start_Column, DPC_File_Date_Time_Length);
            end if;

            Sub_Program_Found := Is_Valid_Subprogram (Trimmed_Line);

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

      if E_Strings.Eq_String (File_Date_Time, E_Strings.Empty_String) then
         File_Date_Time := E_Strings.Copy_String (Str => "Unknown Date (for dpc generation)");
      end if;

   end Extract_DPC_File_Date_Time;

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

   function Is_DPC_Error_Message (Line : E_Strings.T) return Boolean is
   begin
      return E_Strings.Get_Length (E_Str => Line) > 0 and then E_Strings.Get_Element (E_Str => Line,
                                                                                      Pos   => 1) = '!';
   end Is_DPC_Error_Message;

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

   function DPC_Is_New_Range_Line (Line : E_Strings.T) return Boolean is
   begin
      return E_Strings.Eq1_String (E_Str => E_Strings.Section (Line, 1, 17),
                                   Str   => "For path(s) from ") or
        E_Strings.Eq1_String (E_Str => E_Strings.Section (Line, 1, 14),
                              Str   => "For checks of ");
   end DPC_Is_New_Range_Line;

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

   function DPC_Is_New_VC_Line (Line : E_Strings.T) return Boolean is
      Ret_Val : Boolean;
   begin
      -- The shortest possible New VC Line is for a function that has
      -- a single letter identifier, followed by a full-stop e.g.
      --   function_g.
      -- which is 11 characters.
      if E_Strings.Get_Length (E_Str => Line) >= 11 then
         Ret_Val := E_Strings.Eq1_String (E_Str => E_Strings.Section (Line, 1, 10),
                                          Str   => "procedure_")
           or else E_Strings.Eq1_String (E_Str => E_Strings.Section (Line, 1, 9),
                                         Str   => "function_")
           or else E_Strings.Eq1_String (E_Str => E_Strings.Section (Line, 1, 10),
                                         Str   => "task_type_");
         if Ret_Val then
            for I in E_Strings.Lengths range 9 .. E_Strings.Get_Length (E_Str => Line) - 1 loop
               if not (Ada.Characters.Handling.Is_Alphanumeric (E_Strings.Get_Element (E_Str => Line,
                                                                                       Pos   => I))
                         or else E_Strings.Get_Element (E_Str => Line,
                                                        Pos   => I) = '_') then

                  Ret_Val := False;
                  exit;
               end if;
               --# assert I in 9 .. E_Strings.Get_Length (Line) - 1 and
               --#   Line = Line% and
               --#   E_Strings.Get_Length (Line) >= 11;
            end loop;

            if E_Strings.Get_Element (E_Str => Line,
                                      Pos   => E_Strings.Get_Length (E_Str => Line)) /= '.' then
               Ret_Val := False;
            end if;
         end if;
      else
         Ret_Val := False;
      end if;
      return Ret_Val;
   end DPC_Is_New_VC_Line;

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

   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 -- Analyse_DPC_File

   -- open DPC file
   E_Strings.Open
     (File         => DPC_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_DPC_File_Date_Time (DPC_File       => DPC_File,
                               File_Date_Time => The_Date_Time,
                               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, "************* DPC 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, "************* DPC 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;

   if not (Error_In_File) then

      SPARK_IO.New_Line (Report_File, 1);
      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;

      if CommandLine.Data.IgnoreDates then
         SPARK_IO.Put_Line (Report_File, "*** Warning: DPC date stamps ignored ***", 0);
      else
         SPARK_IO.Put_String (Report_File, "DPCs 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      => DPC_File,
                                Success   => Read_Line_Success,
                                File_Line => File_Line);

      if not Read_Line_Success then
         SPARK_IO.Put_Line (SPARK_IO.Standard_Output, "************* DPC file corrupt: no data beyond header ************", 0);
         SPARK_IO.New_Line (SPARK_IO.Standard_Output, 1);
         Error_In_File := True;
      else
         if Is_DPC_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
            -- FileLine buffer
            while not Finished_With_File loop
               -- examine line and act accordingly
               if DPC_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 => DPC_File);

                  ProcessNewRangeLine (File_Line, VC_Info);

               elsif DPC_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_DPC_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;
                        if not VCHeap.Exists (Current_DPC_Name) then
                           VCHeap.Reinitialize
                             (Current_DPC_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);
                        end if;
                        VCHeap.Set_DPC_State (Current_DPC_Name, VCDetails.DPC_SDP_Not_Present);

                     when First_VC_Name =>
                        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);
                        Parsing_State    := New_VC_Name;
                        if not VCHeap.Exists (Current_DPC_Name) then
                           VCHeap.Add
                             (VCHeap.First_Entry,
                              Current_DPC_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_Not_Present,
                              VCDetails.DPC_SDP_Not_Present);
                        else
                           VCHeap.Set_DPC_State (Current_DPC_Name, VCDetails.DPC_SDP_Not_Present);
                        end if;
                     when New_Range =>
                        -- Store a new VC on the VC Heap
                        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);
                        --SPARK_IO.Put_Line(ReportFile,"NewVCNameFound - New range",0);
                        Parsing_State := New_VC_Name;
                        if not VCHeap.Exists (Current_DPC_Name) then
                           VCHeap.Add
                             (VCHeap.First_Entry,
                              Current_DPC_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_Not_Present,
                              VCDetails.DPC_SDP_Not_Present);
                        else
                           VCHeap.Set_DPC_State (Current_DPC_Name, VCDetails.DPC_SDP_Not_Present);
                        end if;
                     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_DPC_Name :=
                          E_Strings.Section
                          (E_Str     => Trimmed_Line,
                           Start_Pos => 1,
                           Length    => E_Strings.Get_Length (E_Str => Trimmed_Line) - 1);
                        --SPARK_IO.Put_Line(ReportFile,"NewVCNameFound - Same range2",0);
                        Parsing_State := New_VC_Name;
                        if not VCHeap.Exists (Current_DPC_Name) then
                           VCHeap.Add
                             (VCHeap.First_Entry,
                              Current_DPC_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_Not_Present,
                              VCDetails.DPC_SDP_Not_Present);
                        else
                           VCHeap.Set_DPC_State (Current_DPC_Name, VCDetails.DPC_SDP_Not_Present);
                        end if;
                     when others =>
                        null;
                  end case;
               end if;

               -- read next line
               Read_Next_Non_Blank_Line (File      => DPC_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 (DPC_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 VCInfo parameters are necessary as WriteVCInfo compares them
            -- in deciding what to write (see definition of WriteVCInfo)
            if VC_Info.Valid then
               -- Reporting is now done as a table, so this has been commented out
               --WriteVCInfo( ReportFile, VCInfo, Dummy );
               null;
            else
               SPARK_IO.Put_Line (Report_File, "No DPCs in file", 0);
            end if;
         end if;

      end if;
   end if;

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

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