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

with CommandLineData;
with ExaminerConstants;
with SystemErrors;

package body Statistics is

   subtype Percentage is Integer range 0 .. 100;

   subtype Index_19 is Integer range 1 .. 19;
   subtype String_19 is String (Index_19);

   subtype Bucket_Index is Integer range 0 .. 10;
   type Bucket_Array is array (Bucket_Index) of Natural;

   type Table_Data_Record is record
      Label          : String_19;
      No_Of_Reports  : Natural;
      Max_Size       : Natural;  -- Signifies a dynamic table when equal to 0
      Min_Units_Used : Natural;
      Max_Units_Used : Natural;
      Buckets        : Bucket_Array;
      Percent_Used   : Percentage;
   end record;

   type Table_Usage_Array is array (Table_Type) of Table_Data_Record;

   -- Initialize data for each of the tables

   TableUsage : Table_Usage_Array :=
     Table_Usage_Array'
     (RelationTable => Table_Data_Record'(Label          => String_19'("Relation Table     "),
                                          No_Of_Reports  => 0,
                                          Max_Size       => 0,
                                          Min_Units_Used => 0,
                                          Max_Units_Used => 0,
                                          Buckets        => Bucket_Array'(others => 0),
                                          Percent_Used   => 0),
      VCGHeap       => Table_Data_Record'(Label          => String_19'("VCG Heap           "),
                                          No_Of_Reports  => 0,
                                          Max_Size       => 0,
                                          Min_Units_Used => 0,
                                          Max_Units_Used => 0,
                                          Buckets        => Bucket_Array'(others => 0),
                                          Percent_Used   => 0),
      StringTable   => Table_Data_Record'(Label          => String_19'("String Table       "),
                                          No_Of_Reports  => 0,
                                          Max_Size       => ExaminerConstants.String_Table_Size,
                                          Min_Units_Used => 0,
                                          Max_Units_Used => 0,
                                          Buckets        => Bucket_Array'(others => 0),
                                          Percent_Used   => 0),
      SymbolTable   => Table_Data_Record'(Label          => String_19'("Symbol Table       "),
                                          No_Of_Reports  => 0,
                                          Max_Size       => 0,
                                          Min_Units_Used => 0,
                                          Max_Units_Used => 0,
                                          Buckets        => Bucket_Array'(others => 0),
                                          Percent_Used   => 0),
      SyntaxTree    => Table_Data_Record'(Label          => String_19'("Syntax Tree        "),
                                          No_Of_Reports  => 0,
                                          Max_Size       => ExaminerConstants.SyntaxTreeSize,
                                          Min_Units_Used => 0,
                                          Max_Units_Used => 0,
                                          Buckets        => Bucket_Array'(others => 0),
                                          Percent_Used   => 0),
      RecordFields  => Table_Data_Record'(Label          => String_19'("Record components  "),
                                          No_Of_Reports  => 0,
                                          Max_Size       => ExaminerConstants.MaxRecordComponents,
                                          Min_Units_Used => 0,
                                          Max_Units_Used => 0,
                                          Buckets        => Bucket_Array'(others => 0),
                                          Percent_Used   => 0),
      RecordErrors  => Table_Data_Record'(Label          => String_19'("Record errors      "),
                                          No_Of_Reports  => 0,
                                          Max_Size       => ExaminerConstants.MaxRecordErrors,
                                          Min_Units_Used => 0,
                                          Max_Units_Used => 0,
                                          Buckets        => Bucket_Array'(others => 0),
                                          Percent_Used   => 0));

   function Calc_Percent (Size     : in Natural;
                          Max_Size : in Natural) return Percentage
   --# pre Max_Size >= Size and
   --#   Max_Size /= 0;
   is
      T : Long_Long_Integer;
      R : Percentage;
   begin
      T := (Long_Long_Integer (Size) * 100) / Long_Long_Integer (Max_Size);

      -- Clip just to be on the safe side
      if T < 0 then
         R := 0;
      elsif T > 100 then
         R := 100;
      else
         R := Percentage (T);
      end if;
      return R;
   end Calc_Percent;

   procedure SetTableUsage (Table : in Table_Type;
                            Size  : in Natural) is
      Max_Size : Integer;
   begin

      Max_Size := TableUsage (Table).Max_Size;

      -- Is the value of "Size" silly (more than maximum)?
      if Max_Size > 0 and then Size > Max_Size then

         SystemErrors.Fatal_Error
           (Sys_Err => SystemErrors.Statistics_Usage_Greater_Than_Table_Size,
            Msg     => "in Statistics.SetTableUsage");

         -- Is more of the table being used than previously recorded?

      else
         if Size > TableUsage (Table).Max_Units_Used then

            -- Update number of units being used in table, and calculate
            -- percentage of table in use.

            TableUsage (Table).Max_Units_Used := Size;

            if Max_Size /= 0 then
               TableUsage (Table).Percent_Used := Calc_Percent (Size, Max_Size);
            end if;
         end if;

         if TableUsage (Table).Min_Units_Used = 0 or else Size < TableUsage (Table).Min_Units_Used then
            TableUsage (Table).Min_Units_Used := Size;
         end if;

         -- Add one to the appropriate size bucket
         -- In the highly unlikely event that a bucket becomes fulll
         -- no more is added.
         if Size >= 2 ** (10 + (Bucket_Index'Last - 1))
           and then TableUsage (Table).Buckets (Bucket_Index'Last) < Integer'Last then
            TableUsage (Table).Buckets (Bucket_Index'Last) := TableUsage (Table).Buckets (Bucket_Index'Last) + 1;
         else
            for I in Bucket_Index range Bucket_Index'First .. Bucket_Index'Last - 1 loop
               if Size < 2 ** (10 + I) and then TableUsage (Table).Buckets (I) < Integer'Last then
                  TableUsage (Table).Buckets (I) := TableUsage (Table).Buckets (I) + 1;
                  exit;
               end if;
            end loop;
         end if;

      end if;
   end SetTableUsage;

   procedure WriteOutput (File : in SPARK_IO.File_Type) is

      Column2Posn : constant Integer := 20;
      Column3Posn : constant Integer := 30;
      Column4Posn : constant Integer := 40;
      Column5Posn : constant Integer := 50;
      LastColumn  : constant Integer := 60;

   begin

      SPARK_IO.New_Line (File    => File,
                         Spacing => 2);

      SPARK_IO.Put_Line (File => File,
                         Item => "Resource statistics",
                         Stop => 0);
      SPARK_IO.New_Line (File    => File,
                         Spacing => 1);

      -- Write header lines
      SPARK_IO.Set_Col (File => File,
                        Posn => Column3Posn);
      SPARK_IO.Put_Line (File => File,
                         Item => "Units Used",
                         Stop => 0);
      SPARK_IO.Put_String (File => File,
                           Item => "Table",
                           Stop => 0);

      SPARK_IO.Set_Col (File => File,
                        Posn => Column3Posn - 5);

      SPARK_IO.Put_String (File => File,
                           Item => "Min",
                           Stop => 0);

      SPARK_IO.Set_Col (File => File,
                        Posn => Column4Posn - 5);

      SPARK_IO.Put_String (File => File,
                           Item => "Max",
                           Stop => 0);

      SPARK_IO.Set_Col (File => File,
                        Posn => Column5Posn - 10);

      SPARK_IO.Put_String (File => File,
                           Item => "  Max Size",
                           Stop => 0);

      SPARK_IO.Set_Col (File => File,
                        Posn => Column5Posn);

      SPARK_IO.Put_String (File => File,
                           Item => "    % used",
                           Stop => 0);

      -- Write a line for each of the tables

      for Table in Table_Type loop

         SPARK_IO.New_Line (File    => File,
                            Spacing => 1);

         SPARK_IO.Put_String (File => File,
                              Item => TableUsage (Table).Label,
                              Stop => 0);

         SPARK_IO.Set_Col (File => File,
                           Posn => Column2Posn);

         SPARK_IO.Put_Integer
           (File  => File,
            Item  => TableUsage (Table).Min_Units_Used,
            Width => Column3Posn - Column2Posn,
            Base  => 10);

         SPARK_IO.Set_Col (File => File,
                           Posn => Column3Posn);

         SPARK_IO.Put_Integer
           (File  => File,
            Item  => TableUsage (Table).Max_Units_Used,
            Width => Column4Posn - Column3Posn,
            Base  => 10);

         SPARK_IO.Set_Col (File => File,
                           Posn => Column4Posn);

         if TableUsage (Table).Max_Size /= 0 then
            SPARK_IO.Put_Integer
              (File  => File,
               Item  => TableUsage (Table).Max_Size,
               Width => Column5Posn - Column4Posn,
               Base  => 10);

            SPARK_IO.Set_Col (File => File,
                              Posn => Column5Posn);

            SPARK_IO.Put_Integer
              (File  => File,
               Item  => TableUsage (Table).Percent_Used,
               Width => LastColumn - Column5Posn,
               Base  => 10);
         else
            SPARK_IO.Put_String (File => File,
                                 Item => "   Dynamic",
                                 Stop => 0);

            SPARK_IO.Set_Col (File => File,
                              Posn => Column5Posn);

            SPARK_IO.Put_String (File => File,
                                 Item => "   Dynamic",
                                 Stop => 0);
         end if;

      end loop;

      SPARK_IO.New_Line (File    => File,
                         Spacing => 1);

      if CommandLineData.Content.Debug.Extra_Stats then
         -- Write out histogram
         SPARK_IO.New_Line (File    => File,
                            Spacing => 1);
         SPARK_IO.Put_Line (File => File,
                            Item => "Histogram",
                            Stop => 0);

         SPARK_IO.Put_String (File => File,
                              Item => "Table               ",
                              Stop => 0);

         -- Note use '^' here rather than "**" to save horizontal space so the
         -- whole histogram fits in 80 characters.
         SPARK_IO.Put_String (File => File,
                              Item => "2^10 2^11 2^12 2^13 2^14 2^15 2^16 2^17 2^18 2^19 2^20",
                              Stop => 0);
         SPARK_IO.New_Line (File    => File,
                            Spacing => 1);

         for Table in Table_Type loop
            SPARK_IO.Put_String (File => File,
                                 Item => TableUsage (Table).Label,
                                 Stop => 0);
            for I in Bucket_Index loop
               SPARK_IO.Set_Col (File => File,
                                 Posn => (Index_19'Last + 3) + (5 * I));

               SPARK_IO.Put_Integer (File  => File,
                                     Item  => TableUsage (Table).Buckets (I),
                                     Width => 0,
                                     Base  => 10);
            end loop;
            SPARK_IO.New_Line (File    => File,
                               Spacing => 1);
         end loop;
      end if;

   end WriteOutput;

end Statistics;
