-------------------------------------------------------------------------------
-- (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 print the VC report                                            --
--                                                                            --
--------------------------------------------------------------------------------

with PathFormatter;
with SPARK_IO;
with VCDetails;

separate (VCS)
procedure PrintVCReport
  (VC_Filename          : in E_Strings.T;
   SIV_Filename         : in E_Strings.T;
   VCTR_Filename        : in E_Strings.T;
   RSM_Filename         : in E_Strings.T;
   PLG_Filename         : in E_Strings.T;
   SLG_Filename         : in E_Strings.T;
   VC_Error             : in Boolean;
   SIV_Error            : in Boolean;
   VCTR_Error           : in Boolean;
   RSM_Error            : in Boolean;
   PLG_Error            : in Boolean;
   SLG_Error            : in Boolean;
   SLG_File_Missing     : in Boolean;
   REV_Errors           : in Review_Errors;
   Report_File          : in SPARK_IO.File_Type;
   Temp_File            : in SPARK_IO.File_Type;
   Temp_False_File      : in SPARK_IO.File_Type;
   Temp_Contra_File     : in SPARK_IO.File_Type;
   Temp_Victor_File     : in SPARK_IO.File_Type;
   Temp_Riposte_File    : in SPARK_IO.File_Type;
   Temp_User_File       : in SPARK_IO.File_Type;
   Temp_PR_Verr_File    : in SPARK_IO.File_Type;
   Temp_Warn_Error_File : in SPARK_IO.File_Type)
is
   Table_Pad_Width : constant Integer := 44;

   Proved_By_Col_Width   : constant Integer := 20;
   Dead_Path_Col_Width   : constant Integer := 11;
   Status_Code_Str_Width : constant Integer := 1;

   subtype Proved_By_Str_Index is Integer range 1 .. Proved_By_Col_Width;
   subtype Dead_Path_Str_Index is Integer range 1 .. Dead_Path_Col_Width;

   subtype Status_Code_Str_Index is Integer range 1 .. Status_Code_Str_Width;

   subtype Proved_By_Str_T is String (Proved_By_Str_Index);
   subtype Dead_Path_Str_T is String (Dead_Path_Str_Index);
   subtype Status_Code_Str_T is String (Status_Code_Str_Index);

   type Proved_By_Str_Array is array (VCDetails.VC_State_T) of Proved_By_Str_T;
   type Dead_Path_Str_Array is array (VCDetails.DPC_State_T) of Dead_Path_Str_T;

   type Proved_By_Status_Code_Str_Array is array (VCDetails.VC_State_T) of Status_Code_Str_T;
   type Dead_Path_Status_Code_Str_Array is array (VCDetails.DPC_State_T) of Status_Code_Str_T;

   Proved_By_Str : constant Proved_By_Str_Array :=
     Proved_By_Str_Array'
     (VCDetails.VC_Not_Present                   => " No VCG             ",
      VCDetails.VC_SIV_Not_Present               => " No SIV             ",
      VCDetails.VC_Undischarged                  => " Undischarged       ",
      VCDetails.VC_Proved_By_Examiner            => " Examiner           ",
      VCDetails.VC_Proved_By_Inference           => " Inference          ",
      VCDetails.VC_Proved_By_Contradiction       => " Contradiction      ",
      VCDetails.VC_Proved_Using_User_Proof_Rules => " User Rules         ",
      VCDetails.VC_Proved_By_Victor              => " Victor             ",
      VCDetails.VC_Proved_By_Riposte             => " Riposte            ",
      VCDetails.VC_Proved_By_Checker             => " Checker            ",
      VCDetails.VC_Proved_By_Review              => " Review             ",
      VCDetails.VC_False                         => " False              ");

   Dead_Path_Str : constant Dead_Path_Str_Array :=
     Dead_Path_Str_Array'
     (VCDetails.DPC_Not_Present     => " No DPC    ",
      VCDetails.DPC_SDP_Not_Present => " No SDP    ",
      VCDetails.DPC_Unchecked       => " Unchecked ",
      VCDetails.DPC_Live            => " Live      ",
      VCDetails.DPC_Dead            => " Dead      ");

   Proved_By_Status_Code : constant Proved_By_Status_Code_Str_Array :=
     Proved_By_Status_Code_Str_Array'
     (VCDetails.VC_Not_Present                   => "-",
      VCDetails.VC_SIV_Not_Present               => "S",
      VCDetails.VC_Undischarged                  => "U",
      VCDetails.VC_Proved_By_Examiner            => "E",
      VCDetails.VC_Proved_By_Inference           => "I",
      VCDetails.VC_Proved_By_Contradiction       => "X",
      VCDetails.VC_Proved_By_Checker             => "C",
      VCDetails.VC_Proved_By_Review              => "R",
      VCDetails.VC_Proved_Using_User_Proof_Rules => "P",
      VCDetails.VC_Proved_By_Victor              => "V",
      VCDetails.VC_Proved_By_Riposte             => "O",
      VCDetails.VC_False                         => "F");

   Dead_Path_Status_Code : constant Dead_Path_Status_Code_Str_Array :=
     Dead_Path_Status_Code_Str_Array'
     (VCDetails.DPC_Not_Present     => "-",
      VCDetails.DPC_SDP_Not_Present => "S",
      VCDetails.DPC_Unchecked       => "U",
      VCDetails.DPC_Live            => "L",
      VCDetails.DPC_Dead            => "D");

   VC_Success               : Boolean;
   This_VC                  : Heap.Atom;
   Next_VC                  : Heap.Atom;
   VC_Number                : E_Strings.T;
   Table_Line               : E_Strings.T;
   VC_Name_Prefix           : E_Strings.T;
   VC_Name_Tabulation       : Integer;
   VC_Path_Start_Tabulation : Integer;
   VC_Path_End_Tabulation   : Integer;
   This_VC_Name             : E_Strings.T;
   This_VC_Path_Start       : E_Strings.T;
   This_VC_Path_End         : E_Strings.T;
   This_VC_End_Type         : VCDetails.Terminal_Point_Type;

   This_VC_State  : VCDetails.VC_State_T;
   This_DPC_State : VCDetails.DPC_State_T;

   Num_Undischarged : Integer;
   Num_False        : Integer;
   Num_Contra       : Integer;
   Num_User         : Integer;
   Num_Victor       : Integer;
   Num_Riposte      : Integer;

   Duplicated            : Boolean;
   Unused_Pos            : Integer;
   Non_Duplicated_Errors : Integer;

   --------------------------------------------------------------------------
   procedure Add_Padding (Line    : in out E_Strings.T;
                          Length  : in     Integer;
                          Padding : in     String)

   --# derives Line from *,
   --#                   Length,
   --#                   Padding;
   is
   begin -- Add_Padding

      while E_Strings.Get_Length (E_Str => Line) < Length loop
         E_Strings.Append_String (E_Str => Line,
                                  Str   => Padding);
      end loop;

   end Add_Padding;

begin --PrintVCReport
   Num_Undischarged := 0;
   Num_False        := 0;
   Num_Contra       := 0;
   Num_User         := 0;
   Num_Victor       := 0;
   Num_Riposte      := 0;
   -- Num_Of_Dead_Paths := 0;
   VC_Name_Prefix := VCHeap.Get_VC_Name_Prefix;

   -- If there any warnings or errors found in analysing the VCG,
   -- SIV or PLG file, record the filname of the errant file to a
   -- temporary warning and error file for reporting in the overall
   -- or short summary generated by the Total package.
   if VC_Error then
      SPARK_IO.Put_Integer (Temp_Warn_Error_File, 1, 4, 10);
      E_Strings.Put_Line (File  => Temp_Warn_Error_File,
                          E_Str => PathFormatter.Format (VC_Filename));
   end if;

   --# assert True;
   if SIV_Error then
      SPARK_IO.Put_Integer (Temp_Warn_Error_File, 1, 4, 10);
      E_Strings.Put_Line (File  => Temp_Warn_Error_File,
                          E_Str => PathFormatter.Format (SIV_Filename));
   end if;

   --# assert True;
   if VCTR_Error then
      SPARK_IO.Put_Integer (Temp_Warn_Error_File, 1, 4, 10);
      E_Strings.Put_Line (File  => Temp_Warn_Error_File,
                          E_Str => PathFormatter.Format (VCTR_Filename));
   end if;

   --# assert True;
   if RSM_Error then
      SPARK_IO.Put_Integer (Temp_Warn_Error_File, 1, 4, 10);
      E_Strings.Put_Line (File  => Temp_Warn_Error_File,
                          E_Str => PathFormatter.Format (RSM_Filename));
   end if;

   --# assert True;
   if PLG_Error then
      SPARK_IO.Put_Integer (Temp_Warn_Error_File, 1, 4, 10);
      E_Strings.Put_Line (File  => Temp_Warn_Error_File,
                          E_Str => PathFormatter.Format (PLG_Filename));
   end if;

   --# assert True;
   if (SLG_Error or SLG_File_Missing) then
      SPARK_IO.Put_Integer (Temp_Warn_Error_File, 1, 4, 10);
      E_Strings.Put_Line (File  => Temp_Warn_Error_File,
                          E_Str => PathFormatter.Format (SLG_Filename));
   end if;

   --# assert True;
   if REV_Errors.Last_Error /= Errors_Index'First then
      --  don't count any errors which are duplicated in review file
      Non_Duplicated_Errors := REV_Errors.Last_Error - 1;
      for I in Integer range (Errors_Index'First + 1) .. REV_Errors.Last_Error loop
         --# accept F, 10, Unused_Pos, "Unused_Pos unused here";
         E_Strings.Find_Sub_String
           (E_Str         => REV_Errors.Error_List (I),
            Search_String => "duplicated",
            String_Found  => Duplicated,
            String_Start  => Unused_Pos);
         --# end accept;

         --# assert Non_Duplicated_Errors < REV_Errors.Last_Error
         --#   and Non_Duplicated_Errors >= (REV_Errors.Last_Error - 1) - (I - (Errors_Index'First + 1))
         --#   and REV_Errors.Last_Error /= Errors_Index'First
         --#   and Non_Duplicated_Errors >= -I + REV_Errors.Last_Error;

         if Duplicated then
            Non_Duplicated_Errors := Non_Duplicated_Errors - 1;
         end if;
      end loop;

      --# assert True;
      --  if there are any (nonduplicated) errors in the file, print number and VC name
      if Non_Duplicated_Errors >= 1 then
         if REV_Errors.Excess_Count > 0 then
            SPARK_IO.Put_Line (Report_File, "There were too many errors", 0);
         else
            SPARK_IO.Put_Integer (Temp_PR_Verr_File, Non_Duplicated_Errors, 4, 10);
         end if;
         E_Strings.Put_Line (File  => Temp_PR_Verr_File,
                             E_Str => PathFormatter.Format (VC_Filename));
      end if;
   end if;

   --# assert True;

   -- Only display the table if there were no errors for this proof object.
   -- Note than a missing SLG file is not treated as an error.
   -- Some organisations may deliberately delete SLG files, to
   -- save space.
   if not (VC_Error or SIV_Error or VCTR_Error or SLG_Error or PLG_Error or REV_Errors.Last_Error > Errors_Index'First) then

      SPARK_IO.New_Line (Report_File, 1);
      Table_Line := E_Strings.Copy_String (Str => "VCs for ");
      E_Strings.Append_Examiner_String (E_Str1 => Table_Line,
                                        E_Str2 => VC_Name_Prefix);
      E_Strings.Append_String (E_Str => Table_Line,
                               Str   => " :");
      E_Strings.Put_Line (File  => Report_File,
                          E_Str => Table_Line);

      VC_Name_Tabulation := VCHeap.Get_Longest_VC_Name_Length - E_Strings.Get_Length (E_Str => VC_Name_Prefix);
      -- Give at least 5 columns (including a leading space)
      -- for the VC number, so subprograms with up to 9999
      -- VCs all come out looking the same.
      if VC_Name_Tabulation < 5 then
         VC_Name_Tabulation := 5;
      end if;

      VC_Path_Start_Tabulation := 3 + (VC_Name_Tabulation + VCHeap.Get_Longest_VC_Start_Length);
      VC_Path_End_Tabulation   := 3 + (VC_Path_Start_Tabulation + VCHeap.Get_Longest_VC_End_Length);

      --# assert True;

      -- Print the table header
      Table_Line := E_Strings.Copy_String (Str => " -");
      Add_Padding (Line    => Table_Line,
                   Length  => VC_Path_End_Tabulation + Table_Pad_Width,
                   Padding => "-");

      E_Strings.Put_Line (File  => Report_File,
                          E_Str => Table_Line);

      Table_Line := E_Strings.Copy_String (Str => "| # ");
      Add_Padding (Line    => Table_Line,
                   Length  => VC_Name_Tabulation,
                   Padding => " ");
      E_Strings.Append_String (E_Str => Table_Line,
                               Str   => " | From ");
      Add_Padding (Line    => Table_Line,
                   Length  => VC_Path_Start_Tabulation,
                   Padding => " ");
      E_Strings.Append_String (E_Str => Table_Line,
                               Str   => " | To ");
      Add_Padding (Line    => Table_Line,
                   Length  => VC_Path_End_Tabulation,
                   Padding => " ");

      E_Strings.Append_String (E_Str => Table_Line,
                               Str   => "  | Proved By          | Dead Path | Status |");

      E_Strings.Put_Line (File  => Report_File,
                          E_Str => Table_Line);

      Table_Line := E_Strings.Copy_String (Str => "|-");
      Add_Padding (Line    => Table_Line,
                   Length  => VC_Path_End_Tabulation + Table_Pad_Width,
                   Padding => "-");

      E_Strings.Put_Line (File  => Report_File,
                          E_Str => Table_Line);

      -- Now loop through the VCHeap and print one table line per VC
      This_VC    := VCHeap.First_Entry;
      VC_Success := True;

      while not Heap.IsNullPointer (This_VC) and VC_Success loop

         VCHeap.Details
           (This_VC,
            This_VC_Name,
            This_VC_Path_Start,
            This_VC_Path_End,
            This_VC_End_Type,
            This_VC_State,
            This_DPC_State);

         -- trim the VC name prefix from the VC_Name to get VC number
         -- as we only print the VC number in the table

         VC_Number :=
           E_Strings.Section
           (E_Str     => This_VC_Name,
            Start_Pos => E_Strings.Get_Length (E_Str => VC_Name_Prefix) + 2,
            Length    => E_Strings.Get_Length (E_Str => This_VC_Name) -
              (E_Strings.Get_Length (E_Str => VC_Name_Prefix) + 1));

         -- Start composing the table line for this VC
         Table_Line := E_Strings.Copy_String (Str => "| ");
         E_Strings.Append_Examiner_String (E_Str1 => Table_Line,
                                           E_Str2 => VC_Number);

         -- pad with spaces to longest VC number length
         Add_Padding (Line    => Table_Line,
                      Length  => VC_Name_Tabulation,
                      Padding => " ");

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => " | ");
         E_Strings.Append_Examiner_String (E_Str1 => Table_Line,
                                           E_Str2 => This_VC_Path_Start);
         Add_Padding (Line    => Table_Line,
                      Length  => VC_Path_Start_Tabulation,
                      Padding => " ");

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => " | ");

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => VCDetails.End_Type_Image (This_VC_End_Type));
         E_Strings.Append_Examiner_String (E_Str1 => Table_Line,
                                           E_Str2 => This_VC_Path_End);
         Add_Padding (Line    => Table_Line,
                      Length  => VC_Path_End_Tabulation,
                      Padding => " ");

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => "  |");

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => Proved_By_Str (This_VC_State));

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => "|");

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => Dead_Path_Str (This_DPC_State));

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => "|   ");

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => Proved_By_Status_Code (This_VC_State));

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => Dead_Path_Status_Code (This_DPC_State));

         E_Strings.Append_String (E_Str => Table_Line,
                                  Str   => "   |");

         -- Now print the table line and get info for the next VC

         --# assert True;

         case This_VC_State is
            when VCDetails.VC_SIV_Not_Present | VCDetails.VC_Undischarged =>
               Num_Undischarged := Num_Undischarged + 1;
            when VCDetails.VC_Proved_By_Contradiction =>
               Num_Contra := Num_Contra + 1;
            when VCDetails.VC_Proved_Using_User_Proof_Rules =>
               Num_User := Num_User + 1;
            when VCDetails.VC_False =>
               Num_False := Num_False + 1;
            when VCDetails.VC_Proved_By_Victor =>
               Num_Victor := Num_Victor + 1;
            when VCDetails.VC_Proved_By_Riposte =>
               Num_Riposte := Num_Riposte + 1;
            when others =>
               null;
         end case;

         --# assert True;

         E_Strings.Put_Line (File  => Report_File,
                             E_Str => Table_Line);

         VCHeap.Next (This_VC, VC_Success, Next_VC);

         This_VC := Next_VC;

      end loop;

      --# assert True;
      if Num_Contra > 0 then
         SPARK_IO.Put_Integer (Temp_Contra_File, Num_Contra, 4, 10);
         E_Strings.Put_Line (File  => Temp_Contra_File,
                             E_Str => PathFormatter.Format (VC_Filename));
      end if;

      --# assert True;
      if Num_Victor > 0 then
         SPARK_IO.Put_Integer (Temp_Victor_File, Num_Victor, 4, 10);
         E_Strings.Put_Line (File  => Temp_Victor_File,
                             E_Str => PathFormatter.Format (VC_Filename));
      end if;

      --# assert True;
      if Num_Riposte > 0 then
         SPARK_IO.Put_Integer (Temp_Riposte_File, Num_Riposte, 4, 10);
         E_Strings.Put_Line (File  => Temp_Riposte_File,
                             E_Str => PathFormatter.Format (VC_Filename));
      end if;

      --# assert True;
      if Num_User > 0 then
         SPARK_IO.Put_Integer (Temp_User_File, Num_User, 4, 10);
         E_Strings.Put_Line (File  => Temp_User_File,
                             E_Str => PathFormatter.Format (VC_Filename));
      end if;

      --# assert True;
      if Num_False > 0 then
         SPARK_IO.Put_Integer (Temp_False_File, Num_False, 4, 10);
         E_Strings.Put_Line (File  => Temp_False_File,
                             E_Str => PathFormatter.Format (VC_Filename));
      end if;

      --# assert True;
      -- if the subprogram contains any undischarged VCs add the name and number to Temp_File
      if Num_Undischarged > 0 then
         SPARK_IO.Put_Integer (Temp_File, Num_Undischarged, 4, 10);
         E_Strings.Put_Line (File  => Temp_File,
                             E_Str => PathFormatter.Format (VC_Filename));
      end if;

      -- Print the table footer.
      Table_Line := E_Strings.Copy_String (Str => " -");

      Add_Padding (Line    => Table_Line,
                   Length  => VC_Path_End_Tabulation + Table_Pad_Width,
                   Padding => "-");

      E_Strings.Put_Line (File  => Report_File,
                          E_Str => Table_Line);

      SPARK_IO.New_Line (Report_File, 1);
   end if;

   --# accept F, 33, Unused_Pos, "Unused_Pos unused here";
end PrintVCReport;
