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

separate (VCS)
procedure AnalyseVictoredVCFile
  (Report_File            : in     SPARK_IO.File_Type;
   Filename               : in     E_Strings.T;
   Error_In_VCTR_File     :    out Boolean;
   File_Error             :    out E_Strings.T;
   Temp_Victor_Error_File : in     SPARK_IO.File_Type)
is

   --  String and error conctants.
   Str_File_Corrupt : constant String := "ViCToR file corrupt: Could not parse line";
   Str_Cannot_Open  : constant String := "Cannot open ViCToR results file";

   --  VCs or goals must start at 1
   Invalid_VC_Number       : constant Natural := 0;
   Blank_Conclusion_Number : constant Natural := 0;

   type Victor_Status_T is (Victor_True, Victor_Unproven, Victor_Error);
   type VC_Type_T is (VC_Type_Procedure, VC_Type_Function, VC_Type_Task_Type, VC_Type_Unknown);

   --  As defined in the ViCToR User Manual, Release 0.7.1
   type Victor_CSV_Line_T is record
      VC_Path                      : E_Strings.T;
      Unit                         : E_Strings.T;
      VC_Type                      : VC_Type_T;
      Source                       : E_Strings.T;
      Destination                  : E_Strings.T;
      VC_Number                    : Natural;
      Conclusion_Number            : Natural;
      Status                       : Victor_Status_T;
      Proof_Time                   : E_Strings.T;
      Remarks                      : E_Strings.T;
      Operator_Kinds_In_Hypothesis : E_Strings.T;
      Operator_Kinds_In_Conclusion : E_Strings.T;
   end record;

   Invalid_CSV_Line : constant Victor_CSV_Line_T :=
     Victor_CSV_Line_T'
     (VC_Path                      => E_Strings.Empty_String,
      Unit                         => E_Strings.Empty_String,
      VC_Type                      => VC_Type_Unknown,
      Source                       => E_Strings.Empty_String,
      Destination                  => E_Strings.Empty_String,
      VC_Number                    => Invalid_VC_Number,
      Conclusion_Number            => Blank_Conclusion_Number,
      Status                       => Victor_Error,
      Proof_Time                   => E_Strings.Empty_String,
      Remarks                      => E_Strings.Empty_String,
      Operator_Kinds_In_Hypothesis => E_Strings.Empty_String,
      Operator_Kinds_In_Conclusion => E_Strings.Empty_String);

   File_Status          : SPARK_IO.File_Status;
   Victored_VC_File     : SPARK_IO.File_Type;
   Line_Read            : E_Strings.T;
   Trimmed_Line         : E_Strings.T;
   Success              : Boolean;
   CSV_Line             : Victor_CSV_Line_T;
   Error_Flag_Mentioned : Boolean;

   function Unqote (E_Str : E_Strings.T) return E_Strings.T is
      L      : E_Strings.Lengths;
      Retval : E_Strings.T;
   begin
      L := E_Strings.Get_Length (E_Str);
      if L >= 2
        and then E_Strings.Get_Element (E_Str, E_Strings.Positions'First) = '"'
        and then E_Strings.Get_Element (E_Str, L) = '"' then
         Retval := E_Strings.Section (E_Str     => E_Str,
                                      Start_Pos => E_Strings.Positions'First + 1,
                                      Length    => L - 2);
      else
         Retval := E_Str;
      end if;
      return Retval;
   end Unqote;

   procedure Parse_CSV_String
     (CSV_Line          : in     E_Strings.T;
      CSV_Line_Position : in out E_Strings.Positions;
      The_String        :    out E_Strings.T;
      Ok                :    out Boolean;
      Expect_EOL        : in     Boolean)
   --# derives CSV_Line_Position,
   --#         Ok,
   --#         The_String        from CSV_Line,
   --#                                CSV_Line_Position,
   --#                                Expect_EOL;
   --# pre CSV_Line_Position <= E_Strings.Get_Length (CSV_Line) + 1;
   --# post CSV_Line_Position <= E_Strings.Get_Length (CSV_Line) + 1;
   is
      Is_Quoted_String : Boolean;
      In_Quoted_String : Boolean;
      Comma_Found      : Boolean;
      EOL_Found        : Boolean;
      End_Position     : E_Strings.Positions;
      Tmp_String       : E_Strings.T;
   begin
      Is_Quoted_String := E_Strings.Get_Element (CSV_Line, CSV_Line_Position) = '"';
      In_Quoted_String := Is_Quoted_String;
      Comma_Found      := False;
      End_Position     := CSV_Line_Position;
      for I in E_Strings.Positions range CSV_Line_Position .. E_Strings.Get_Length (CSV_Line) loop
         --# assert E_Strings.Get_Length (CSV_Line) <= E_Strings.Lengths'Last;
         End_Position := I;
         case E_Strings.Get_Element (CSV_Line, I) is
            when '"' =>
               if In_Quoted_String and I > CSV_Line_Position then
                  In_Quoted_String := False;
               end if;
            when ',' =>
               if not In_Quoted_String then
                  Comma_Found := True;
               end if;
            when others =>
               null;
         end case;
         exit when Comma_Found;
      end loop;

      --# assert End_Position >= CSV_Line_Position and End_Position <= E_Strings.Get_Length (CSV_Line) + 1
      --#   and (Comma_Found -> (End_Position <= E_Strings.Get_Length (CSV_Line)));

      --  Work out if we hit the end of line.
      EOL_Found := not Comma_Found or else E_Strings.Get_Length (CSV_Line) = E_Strings.Lengths'Last;

      --  Make sure we found a comma if we were looking for one.
      Ok := Expect_EOL = EOL_Found;

      --  Make sure any quoted strings are OK.
      Ok := Ok and not In_Quoted_String;

      if Ok then
         Tmp_String :=
           E_Strings.Section (E_Str     => CSV_Line,
                              Start_Pos => CSV_Line_Position,
                              Length    => End_Position - CSV_Line_Position);

         --  Strip away the quotes, if necessary.
         if Is_Quoted_String then
            The_String := Unqote (Tmp_String);
         else
            The_String := Tmp_String;
         end if;

         --  Jump over the comma.
         if not EOL_Found then
            CSV_Line_Position := End_Position + 1;
         end if;
      else
         The_String := E_Strings.Empty_String;
      end if;
   end Parse_CSV_String;

   function Parse_Natural (E_Str          : E_Strings.T;
                           Value_On_Error : Natural) return Natural is
      Tmp_Integer : Integer;
      The_Natural : Natural;
      Tmp_Stop    : Integer;
   begin
      The_Natural := Value_On_Error;
      if E_Strings.Get_Length (E_Str) > 0 then
         --# accept F, 10, Tmp_Stop, "We don't care about Stop at the moment";
         E_Strings.Get_Int_From_String
           (Source   => E_Str,
            Item     => Tmp_Integer,
            Start_Pt => E_Strings.Positions'First,
            Stop     => Tmp_Stop);
         --# end accept;

         -- TODO: Check that Tmp_Stop = E_Strings.Get_Length (Tmp_String) ?

         if Tmp_Integer >= Natural'First then
            The_Natural := Natural'(Tmp_Integer);
         end if;
      end if;
      --# accept F, 33, Tmp_Stop, "We don't care about Stop at the moment";
      return The_Natural;
   end Parse_Natural;

   function Parse_Victor_Status (E_Str          : E_Strings.T;
                                 Value_On_Error : Victor_Status_T) return Victor_Status_T is
      The_Victor_Status : Victor_Status_T;
   begin
      The_Victor_Status := Value_On_Error;
      if E_Strings.Eq1_String (E_Str, "true") then
         The_Victor_Status := Victor_True;
      elsif E_Strings.Eq1_String (E_Str, "unproven") then
         The_Victor_Status := Victor_Unproven;
      elsif E_Strings.Eq1_String (E_Str, "error") then
         The_Victor_Status := Victor_Error;
      end if;
      return The_Victor_Status;
   end Parse_Victor_Status;

   function Parse_VC_Type (E_Str : E_Strings.T) return VC_Type_T is
      The_Type : VC_Type_T := VC_Type_Unknown;
   begin
      if E_Strings.Eq1_String (E_Str, "procedure") then
         The_Type := VC_Type_Procedure;
      elsif E_Strings.Eq1_String (E_Str, "function") then
         The_Type := VC_Type_Function;
      elsif E_Strings.Eq1_String (E_Str, "task_type") then
         The_Type := VC_Type_Task_Type;
      end if;
      return The_Type;
   end Parse_VC_Type;

   --  This function will construct the full unit name out the various
   --  fields in each csv row. So, for example given the following row:
   --     ap_/altitude,maintain,procedure,,,1,,true,0,,,
   --  We should get the following string back:
   --     procedure_maintain_1
   function Full_Unit_Name (The_Record : in Victor_CSV_Line_T) return E_Strings.T is
      Tmp_Number : E_Strings.T;
      Tmp        : E_Strings.T := E_Strings.Empty_String;
   begin
      case The_Record.VC_Type is
         when VC_Type_Procedure =>
            E_Strings.Append_String (Tmp, "procedure_");
         when VC_Type_Function =>
            E_Strings.Append_String (Tmp, "function_");
         when VC_Type_Task_Type =>
            E_Strings.Append_String (Tmp, "task_type_");
         when VC_Type_Unknown =>
            null;
      end case;
      E_Strings.Append_Examiner_String (Tmp, The_Record.Unit);
      if The_Record.VC_Number /= Invalid_VC_Number then
         E_Strings.Append_Char (Tmp, '_');
         E_Strings.Put_Int_To_String
           (Dest     => Tmp_Number,
            Item     => The_Record.VC_Number,
            Start_Pt => E_Strings.Positions'First,
            Base     => 10);
         E_Strings.Append_Examiner_String (Tmp, Tmp_Number);
      end if;
      return Tmp;
   end Full_Unit_Name;

   procedure Parse_Victor_CSV_Line (The_Line   : in     E_Strings.T;
                                    The_Record :    out Victor_CSV_Line_T;
                                    Ok         :    out Boolean)
   --# derives Ok,
   --#         The_Record from The_Line;
   --# pre E_Strings.Get_Length (The_Line) >= 1;
   is
      Current_Position : E_Strings.Positions := E_Strings.Positions'First;
      Tmp              : E_Strings.T;

      Num_CSV_Entries : constant Natural := 12;
      subtype CSV_Record_Index is Natural range 1 .. Num_CSV_Entries;
   begin
      The_Record := Invalid_CSV_Line;

      for I in CSV_Record_Index
      --# assert Current_Position <= E_Strings.Get_Length (The_Line) + 1;
      loop
         Parse_CSV_String
           (CSV_Line          => The_Line,
            CSV_Line_Position => Current_Position,
            The_String        => Tmp,
            Ok                => Ok,
            Expect_EOL        => (I = CSV_Record_Index'Last));
         exit when not Ok;
         --  Each I will map to each number given in the ViCToR user
         --  manual describing the "CSV output file".
         case I is
            when 1 =>
               The_Record.VC_Path := Tmp;
            when 2 =>
               The_Record.Unit := Tmp;
            when 3 =>
               The_Record.VC_Type := Parse_VC_Type (Tmp);
            when 4 =>
               The_Record.Source := Tmp;
            when 5 =>
               The_Record.Destination := Tmp;
            when 6 =>
               The_Record.VC_Number := Parse_Natural (Tmp, Invalid_VC_Number);
            when 7 =>
               The_Record.Conclusion_Number := Parse_Natural (Tmp, Blank_Conclusion_Number);
            when 8 =>
               The_Record.Status := Parse_Victor_Status (Tmp, Victor_Error);
            when 9 =>
               The_Record.Proof_Time := Tmp;
            when 10 =>
               The_Record.Remarks := Tmp;
            when 11 =>
               The_Record.Operator_Kinds_In_Hypothesis := Tmp;
            when 12 =>
               The_Record.Operator_Kinds_In_Conclusion := Tmp;
         end case;
      end loop;
   end Parse_Victor_CSV_Line;

begin -- AnalyseVictoredVCFile
   Error_In_VCTR_File   := False;
   File_Error           := E_Strings.Empty_String;
   Victored_VC_File     := SPARK_IO.Null_File;
   Error_Flag_Mentioned := False;

   -- open ViCToR results file
   E_Strings.Open
     (File         => Victored_VC_File,
      Mode_Of_File => SPARK_IO.In_File,
      Name_Of_File => Filename,
      Form_Of_File => "",
      Status       => File_Status);
   if File_Status /= SPARK_IO.Ok then
      Error_In_VCTR_File := True;
      File_Error         := E_Strings.Copy_String (Str_Cannot_Open);
      FatalErrors.Process (FatalErrors.Could_Not_Open_Input_File, E_Strings.Empty_String);
   end if;

   loop
      Read_Next_Non_Blank_Line (File      => Victored_VC_File,
                                Success   => Success,
                                File_Line => Line_Read);
      exit when not Success;
      --# assert Success;

      Trimmed_Line := E_Strings.Trim (Line_Read);
      Success      := E_Strings.Get_Length (Trimmed_Line) >= 1;

      if Success then
         Parse_Victor_CSV_Line (The_Line   => E_Strings.Trim (Line_Read),
                                The_Record => CSV_Line,
                                Ok         => Success);
      else
         CSV_Line := Invalid_CSV_Line;
      end if;

      if not Success then
         --  Notify stdout.
         SPARK_IO.Put_Line (SPARK_IO.Standard_Output, "************* " & Str_File_Corrupt & " ************", 0);
         SPARK_IO.Put_String (SPARK_IO.Standard_Output, "*** Offending line was: [", 0);
         E_Strings.Put_String (SPARK_IO.Standard_Output, Trimmed_Line);
         SPARK_IO.Put_Line (SPARK_IO.Standard_Output, "]", 0);

         SPARK_IO.New_Line (SPARK_IO.Standard_Output, 1);

         --  Also put somthing in the report file.
         SPARK_IO.Put_String (Report_File, "*** " & Str_File_Corrupt & " ***", 0);

         --  And finally set error flags.
         File_Error         := E_Strings.Copy_String (Str_File_Corrupt);
         Error_In_VCTR_File := True;
      end if;

      --# assert True;

      if not Success then
         null;
      elsif (not E_Strings.Eq1_String (CSV_Line.Remarks, "excluded") and
               --  The goal must be blank, otherwise we may not have proved all conclusions.
               CSV_Line.Conclusion_Number = Blank_Conclusion_Number and
               --  We must have a VC number given.
               CSV_Line.VC_Number /= Invalid_VC_Number) then
         case CSV_Line.Status is
            when Victor_True =>
               VCHeap.Set_VC_State (Full_Unit_Name (CSV_Line), VCDetails.VC_Proved_By_Victor);
            when Victor_Unproven =>
               --  We don't do anything in this case.
               null;
            when Victor_Error =>
               --  This means alt-ergo or perhaps vct encountered some
               --  kind of error. We will flag this up and include it
               --  in the final summary. However, we will only do this
               --  once.
               if not Error_Flag_Mentioned then
                  E_Strings.Put_String (File  => Temp_Victor_Error_File,
                                        E_Str => PathFormatter.Format (Filename));
                  SPARK_IO.Put_Char (Temp_Victor_Error_File, ' ');
                  SPARK_IO.Put_Char (Temp_Victor_Error_File, '(');
                  if E_Strings.Is_Empty (CSV_Line.Remarks) then
                     SPARK_IO.Put_String (Temp_Victor_Error_File, "Error flag returned by vct/alt-ergo.", 0);
                  else
                     E_Strings.Put_String (File  => Temp_Victor_Error_File,
                                           E_Str => CSV_Line.Remarks);
                  end if;
                  SPARK_IO.Put_Line (Temp_Victor_Error_File, ")", 0);
                  Error_Flag_Mentioned := True;
               end if;
         end case;
      elsif CSV_Line.VC_Number = Invalid_VC_Number and CSV_Line.Status = Victor_Error then
         --  If we're here this means that victor has failed to
         --  produce useful output, most likely due to a translation
         --  or paring error, but there may be an error message worth
         --  passing up.
         E_Strings.Put_String (File  => Temp_Victor_Error_File,
                               E_Str => PathFormatter.Format (Filename));
         SPARK_IO.Put_Char (Temp_Victor_Error_File, ' ');
         SPARK_IO.Put_Char (Temp_Victor_Error_File, '(');
         if E_Strings.Is_Empty (CSV_Line.Remarks) then
            SPARK_IO.Put_String (Temp_Victor_Error_File, "Unknown Error", 0);
         else
            E_Strings.Put_String (File  => Temp_Victor_Error_File,
                                  E_Str => CSV_Line.Remarks);
         end if;
         SPARK_IO.Put_Line (Temp_Victor_Error_File, ")", 0);
      end if;
   end loop;

   --# accept F, 10, File_Status, "We don't care anymore since we've got everything we came for." &
   --#        F, 10, Victored_VC_File, "Same as above.";
   SPARK_IO.Close (Victored_VC_File, File_Status);
   --# end accept;

   --# accept Flow, 601, FatalErrors.State, Temp_Victor_Error_File, "False coupling through SPARK_IO" &
   --#        Flow, 601, VCHeap.State, Temp_Victor_Error_File, "False coupling through SPARK_IO";
end AnalyseVictoredVCFile;
