-------------------------------------------------------------------------------
-- (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 Ada.Containers.Hashed_Maps;
with Ada.Strings;
with Ada.Strings.Hash_Case_Insensitive;
with CommandLineData;
with E_Strings.Not_SPARK;
with IndexManager.Index_Table_P;
with SPARK_IO;

package body IndexManager.Cache is

   type Unit_Key_Type is record
      Unit          : LexTokenLists.Lists;
      Is_Components : Boolean;
      --  case Is_Components is
      --     when False =>
      Unit_Types    : ContextManager.UnitTypes;
      --  end case;
   end record;

   type Unit_Element_Type is record
      Is_Components   : Boolean;
      --  case Is_Components is
      --     when False =>
      Source_Filename : LexTokenManager.Lex_String;
      --     when True  =>
      Components      : IndexManager.Component_Lists;
      --  end case;
      Index_Filename  : LexTokenManager.Lex_String;
   end record;

# if not SPARK then

   --  The Ada declaration of the hash table.

   function Unit_Hash (Key : Unit_Key_Type) return Ada.Containers.Hash_Type is

      use type Ada.Containers.Hash_Type;

      Return_Val : Ada.Containers.Hash_Type := 0;
   begin
      for I in LexTokenLists.Positions'First .. LexTokenLists.Get_Length (List => Key.Unit) loop
         Return_Val := Return_Val + Ada.Strings.Hash_Case_Insensitive
           (E_Strings.Not_SPARK.Get_String
              (E_Str => LexTokenManager.Lex_String_To_String
                 (Lex_Str => LexTokenLists.Get_Element (List => Key.Unit,
                                                        Pos  => I))));
      end loop;
      return Return_Val;
   end Unit_Hash;

   function Unit_Equivalent_Keys (Left, Right : Unit_Key_Type) return Boolean is
      use type ContextManager.UnitTypes;
   begin
      if CommandLineData.Content.Debug.File_Names then
         SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                              Item => "INDEXMANAGER.CACHE.UNIT_EQUIVALENT_KEYS ",
                              Stop => 0);
         LexTokenLists.Print_List (File => SPARK_IO.Standard_Output,
                                   List => Left.Unit);
         SPARK_IO.Put_Char (File => SPARK_IO.Standard_Output,
                            Item => ' ');
         LexTokenLists.Print_List (File => SPARK_IO.Standard_Output,
                                   List => Right.Unit);
         SPARK_IO.New_Line (File    => SPARK_IO.Standard_Output,
                            Spacing => 1);
      end if;
      return Left.Is_Components = Right.Is_Components and then
        LexTokenLists.Eq_Unit (First_Item => Left.Unit,
                               Second     => Right.Unit) and then
        (Left.Is_Components or else Left.Unit_Types = Right.Unit_Types);
   end Unit_Equivalent_Keys;

   function Eq_Unit_Element_Type (Left, Right : Unit_Element_Type) return Boolean;

   package Unit_Hash_P is new Ada.Containers.Hashed_Maps
     (Key_Type        => Unit_Key_Type,
      Element_Type    => Unit_Element_Type,
      Hash            => Unit_Hash,
      Equivalent_Keys => Unit_Equivalent_Keys,
      "="             => Eq_Unit_Element_Type);

   function Get_Element (Position : Unit_Hash_P.Cursor) return Unit_Element_Type renames Unit_Hash_P.Element;

# else

   --  The SPARK declaration of the hash table.

   --# inherit Cache;
   package Unit_Hash_P is

      type Map is array (Positive) of Cache.Unit_Element_Type;
      type Cursor is private;

      No_Element : constant Cursor;

      procedure Replace_Element
        (Container : in out Map;
         Position  : in     Cursor;
         New_Item  : in     Cache.Unit_Element_Type);
      --# derives Container from *,
      --#                        New_Item,
      --#                        Position;

      procedure Insert
        (Container : in out Map;
         Key       : in     Cache.Unit_Key_Type;
         New_Item  : in     Cache.Unit_Element_Type;
         Position  :    out Cursor;
         Inserted  :    out Boolean);
      --# derives Container,
      --#         Inserted,
      --#         Position  from Container,
      --#                        Key,
      --#                        New_Item;

      function Find (Container : Map;
                     Key       : Cache.Unit_Key_Type) return Cursor;

   private
      --# hide Unit_Hash_P;
   end Unit_Hash_P;

# end if;

   function "=" (Left, Right : Unit_Hash_P.Cursor) return Boolean renames Unit_Hash_P."=";

   The_Unit_Hash : Unit_Hash_P.Map;

# if SPARK then

   --  The SPARK body of the hash table.

   package body Unit_Hash_P is
      --# hide Unit_Hash_P;
   end Unit_Hash_P;

   function Get_Element (Position : Unit_Hash_P.Cursor) return Unit_Element_Type is
      --# hide Get_Element;
   begin
      null;
   end Get_Element;

# end if;

   function Eq_Unit_Element_Type (Left, Right : Unit_Element_Type) return Boolean
   --# global LexTokenManager.State;
   is

      procedure Trace
      --# derives ;
      is
         --# hide Trace;
      begin
         if CommandLineData.Content.Debug.File_Names then
            SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                                 Item => "INDEXMANAGER.CACHE.EQ_UNIT_ELEMENT_TYPE ",
                                 Stop => 0);
            if Left.Is_Components then
               Index_Table_P.Debug_Put_E_Str (E_Str    => LexTokenManager.Lex_String_To_String (Lex_Str => Left.Source_Filename),
                                              New_Line => False);
            else
               SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                                    Item => "NO SOURCE FILENAME",
                                    Stop => 0);
            end if;
            SPARK_IO.Put_Char (File => SPARK_IO.Standard_Output,
                               Item => ' ');
            if Right.Is_Components then
               Index_Table_P.Debug_Put_E_Str (E_Str    => LexTokenManager.Lex_String_To_String (Lex_Str => Right.Source_Filename),
                                              New_Line => False);
            else
               SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                                    Item => "NO SOURCE FILENAME",
                                    Stop => 0);
            end if;
            SPARK_IO.New_Line (File    => SPARK_IO.Standard_Output,
                               Spacing => 1);
         end if;
      end Trace;

      function Eq_Component_Lists (Item1, Item2 : IndexManager.Component_Lists) return Boolean
      --# global LexTokenManager.State;
      is
         Return_Val : Boolean := True;
      begin
         Trace;
         for I in IndexManager.Component_Index loop
            if not LexTokenLists.Eq_Unit (First_Item => Item1 (I),
                                          Second     => Item2 (I)) then
               Return_Val := False;
            end if;
            exit when not Return_Val or else
              (Item1 (I) = LexTokenLists.Null_List and then
                 Item2 (I) = LexTokenLists.Null_List);
         end loop;
         return Return_Val;
      end Eq_Component_Lists;

   begin
      return Left.Is_Components = Right.Is_Components and then
        ((not Left.Is_Components and then
            (LexTokenManager.Lex_String_Case_Sensitive_Compare
               (Lex_Str1 => Left.Source_Filename,
                Lex_Str2 => Right.Source_Filename) = LexTokenManager.Str_Eq)) or else
           (Left.Is_Components and then Eq_Component_Lists (Item1 => Left.Components,
                                                            Item2 => Right.Components)));
   end Eq_Unit_Element_Type;

   procedure Context_Manager_Unit_Types_Image (Unit_Type : in ContextManager.UnitTypes) is
      --# hide Context_Manager_Unit_Types_Image;
   begin
      SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                           Item => ContextManager.UnitTypes'Image (Unit_Type),
                           Stop => 0);
   end Context_Manager_Unit_Types_Image;

   procedure Add_Unit (Unit            : in LexTokenLists.Lists;
                       Unit_Types      : in ContextManager.UnitTypes;
                       Source_Filename : in E_Strings.T;
                       Index_Filename  : in LexTokenManager.Lex_String;
                       Index_Position  : in IndexManager.File_Position) is
      Position                : Unit_Hash_P.Cursor;
      Inserted                : Boolean;
      New_Item                : Unit_Element_Type;
      Lex_Str_Source_Filename : LexTokenManager.Lex_String;
   begin
      if CommandLineData.Content.Debug.File_Names then
         --  Debug
         SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                              Item => "INDEXMANAGER.CACHE.ADD_UNIT ",
                              Stop => 0);
         LexTokenLists.Print_List (File => SPARK_IO.Standard_Output,
                                   List => Unit);
         SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                              Item => " ",
                              Stop => 0);
         Context_Manager_Unit_Types_Image (Unit_Type => Unit_Types);
         SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                              Item => " ",
                              Stop => 0);
         Index_Table_P.Debug_Put_E_Str (E_Str     => Source_Filename,
                                        New_Line => False);
         SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                              Item => " from index file ",
                              Stop => 0);
         Index_Table_P.Debug_Put_E_Str (E_Str     => LexTokenManager.Lex_String_To_String (Lex_Str => Index_Filename),
                                       New_Line => False);
         SPARK_IO.New_Line (File    => SPARK_IO.Standard_Output,
                            Spacing => 1);
      end if;
      --  Try to insert a unit in the hash table.
      LexTokenManager.Insert_Examiner_String (Str     => Source_Filename,
                                              Lex_Str => Lex_Str_Source_Filename);
      New_Item := Unit_Element_Type'(Is_Components   => False,
                                     Source_Filename => Lex_Str_Source_Filename,
                                     Components      => IndexManager.Component_Lists'(others => LexTokenLists.Null_List),
                                     Index_Filename  => Index_Filename);
      Unit_Hash_P.Insert (Container => The_Unit_Hash,
                          Key       => Unit_Key_Type'(Unit          => Unit,
                                                      Is_Components => False,
                                                      Unit_Types    => Unit_Types),
                          New_Item  => New_Item,
                          Position  => Position,
                          Inserted  => Inserted);

      if not Inserted then
         --  The unit has not been inserted in the hash table because
         --  it is already present.

         if Index_Table_P.Is_Aux_File_Ancestor (Parent_Index_Filename => Get_Element (Position => Position).Index_Filename,
                                                Index_Filename        => Index_Filename) then
            if Eq_Unit_Element_Type (Get_Element (Position => Position), New_Item) then
               --  The same values for the same unit at the same level
               --  => duplicate in the index files => raise a warning.
               Index_Table_P.Output_Error (E              => IndexManager.EW_Duplicate,
                                           Source_File    => Index_Filename,
                                           Token_Position => Index_Position,
                                           Token_String   => LexTokenLists.Token_List_To_String (Token_List => Unit));
            else
               --  Two different values for the same unit at the same
               --  level => contradiction in the index files => stop
               --  SPARK.
               Index_Table_P.Output_Error (E              => IndexManager.EF_Contradiction,
                                           Source_File    => Index_Filename,
                                           Token_Position => Index_Position,
                                           Token_String   => LexTokenLists.Token_List_To_String (Token_List => Unit));
            end if;

         elsif Index_Table_P.Is_File_Ancestor (Parent_Filename => Index_Filename,
                                               Filename        => Get_Element (Position => Position).Index_Filename) then
            if CommandLineData.Content.Debug.File_Names then
               --  Debug
               SPARK_IO.Put_Line (File => SPARK_IO.Standard_Output,
                                  Item => "INDEXMANAGER.CACHE.ADD_UNIT : UPDATE THE CACHE",
                                  Stop => 0);
            end if;
            --  Two different values of the same unit but not at the
            --  same level => update the hash table with the more
            --  local definition.
            Unit_Hash_P.Replace_Element (Container => The_Unit_Hash,
                                         Position  => Position,
                                         New_Item  => New_Item);

            if not (LexTokenManager.Lex_String_Case_Insensitive_Compare
                      (Lex_Str1 => Index_Filename,
                       Lex_Str2 => LexTokenManager.Null_String) = LexTokenManager.Str_Eq) and then
              not (LexTokenManager.Lex_String_Case_Insensitive_Compare
                     (Lex_Str1 => Get_Element (Position => Position).Index_Filename,
                      Lex_Str2 => LexTokenManager.Null_String) = LexTokenManager.Str_Eq) and then
              Eq_Unit_Element_Type (Get_Element (Position => Position), New_Item) then
               --  The same values for the same unit but not at the
               --  same level => duplicate in the index files => raise
               --  a warning.
               Index_Table_P.Output_Error (E              => IndexManager.EW_Duplicate,
                                           Source_File    => Index_Filename,
                                           Token_Position => Index_Position,
                                           Token_String   => LexTokenLists.Token_List_To_String (Token_List => Unit));
            end if;

         elsif not (LexTokenManager.Lex_String_Case_Insensitive_Compare
                      (Lex_Str1 => Index_Filename,
                       Lex_Str2 => LexTokenManager.Null_String) = LexTokenManager.Str_Eq) and then
           not (LexTokenManager.Lex_String_Case_Insensitive_Compare
                  (Lex_Str1 => Get_Element (Position => Position).Index_Filename,
                   Lex_Str2 => LexTokenManager.Null_String) = LexTokenManager.Str_Eq) and then
           Eq_Unit_Element_Type (Get_Element (Position => Position), New_Item) then
            --  The same values for the same unit but not at the same
            --  level => duplicate in the index files => raise a
            --  warning.
            Index_Table_P.Output_Error (E              => IndexManager.EW_Duplicate,
                                        Source_File    => Index_Filename,
                                        Token_Position => Index_Position,
                                        Token_String   => LexTokenLists.Token_List_To_String (Token_List => Unit));
         end if;

      end if;
   end Add_Unit;

   procedure Get_Unit (Required_Unit   : in     LexTokenLists.Lists;
                       Unit_Types      : in     ContextManager.UnitTypes;
                       Source_Filename :    out LexTokenManager.Lex_String;
                       Index_Filename  :    out LexTokenManager.Lex_String;
                       Found           :    out Boolean) is
      Position     : Unit_Hash_P.Cursor;
      Unit_Element : Unit_Element_Type;
   begin
      Position := Unit_Hash_P.Find (Container => The_Unit_Hash,
                                    Key       => Unit_Key_Type'(Unit          => Required_Unit,
                                                                Is_Components => False,
                                                                Unit_Types    => Unit_Types));
      if Position = Unit_Hash_P.No_Element then
         Source_Filename := LexTokenManager.Null_String;
         Index_Filename  := LexTokenManager.Null_String;
         Found           := False;
      else
         Unit_Element    := Get_Element (Position => Position);
         Source_Filename := Unit_Element.Source_Filename;
         Index_Filename  := Unit_Element.Index_Filename;
         Found           := True;
      end if;
   end Get_Unit;

   procedure Add_Components (Unit           : in LexTokenLists.Lists;
                             Components     : in IndexManager.Component_Lists;
                             Index_Filename : in LexTokenManager.Lex_String;
                             Index_Position : in IndexManager.File_Position) is
      Position : Unit_Hash_P.Cursor;
      Inserted : Boolean;
      New_Item : Unit_Element_Type;
   begin
      if CommandLineData.Content.Debug.File_Names then
         --  Debug
         SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                              Item => "INDEXMANAGER.CACHE.ADD_COMPONENTS ",
                              Stop => 0);
         LexTokenLists.Print_List (File => SPARK_IO.Standard_Output,
                                   List => Unit);
         SPARK_IO.Put_String (File => SPARK_IO.Standard_Output,
                              Item => " from index file ",
                              Stop => 0);
         Index_Table_P.Debug_Put_E_Str (E_Str     => LexTokenManager.Lex_String_To_String (Lex_Str => Index_Filename),
                                       New_Line => False);
         SPARK_IO.New_Line (File    => SPARK_IO.Standard_Output,
                            Spacing => 1);
      end if;
      --  Try to insert a unit in the hash table.
      New_Item := Unit_Element_Type'(Is_Components   => True,
                                     Source_Filename => LexTokenManager.Null_String,
                                     Components      => Components,
                                     Index_Filename  => Index_Filename);
      Unit_Hash_P.Insert (Container => The_Unit_Hash,
                          Key       => Unit_Key_Type'(Unit          => Unit,
                                                      Is_Components => True,
                                                      Unit_Types    => ContextManager.InvalidUnit),
                          New_Item  => New_Item,
                          Position  => Position,
                          Inserted  => Inserted);

      if not Inserted then
         --  The unit has not been inserted in the hash table because
         --  it is already present.

         if Index_Table_P.Is_Aux_File_Ancestor (Parent_Index_Filename => Get_Element (Position => Position).Index_Filename,
                                                Index_Filename        => Index_Filename) then
            if Eq_Unit_Element_Type (Get_Element (Position => Position), New_Item) then
               --  The same values for the same unit at the same level
               --  => duplicate in the index files => raise a warning.
               Index_Table_P.Output_Error (E              => IndexManager.EW_Duplicate,
                                           Source_File    => Index_Filename,
                                           Token_Position => Index_Position,
                                           Token_String   => LexTokenLists.Token_List_To_String (Token_List => Unit));
            else
               --  Two different values for the same unit at the same
               --  level => contradiction in the index files => stop
               --  SPARK.
               Index_Table_P.Output_Error (E              => IndexManager.EF_Contradiction,
                                           Source_File    => Index_Filename,
                                           Token_Position => Index_Position,
                                           Token_String   => LexTokenLists.Token_List_To_String (Token_List => Unit));
            end if;

         elsif Index_Table_P.Is_File_Ancestor (Parent_Filename => Index_Filename,
                                               Filename        => Get_Element (Position => Position).Index_Filename) then
            if CommandLineData.Content.Debug.File_Names then
               --  Debug
               SPARK_IO.Put_Line (File => SPARK_IO.Standard_Output,
                                  Item => "INDEXMANAGER.CACHE.ADD_COMPONENTS : UPDATE THE CACHE",
                                  Stop => 0);
            end if;
            --  Two different values of the same unit but not at the
            --  same level => update the hash table with the more
            --  local definition.
            Unit_Hash_P.Replace_Element (Container => The_Unit_Hash,
                                         Position  => Position,
                                         New_Item  => New_Item);

            if not (LexTokenManager.Lex_String_Case_Insensitive_Compare
                      (Lex_Str1 => Index_Filename,
                       Lex_Str2 => LexTokenManager.Null_String) = LexTokenManager.Str_Eq) and then
              not (LexTokenManager.Lex_String_Case_Insensitive_Compare
                     (Lex_Str1 => Get_Element (Position => Position).Index_Filename,
                      Lex_Str2 => LexTokenManager.Null_String) = LexTokenManager.Str_Eq) and then
              Eq_Unit_Element_Type (Get_Element (Position => Position), New_Item) then
               --  The same values for the same unit but not at the
               --  same level => duplicate in the index files => raise
               --  a warning.
               Index_Table_P.Output_Error (E              => IndexManager.EW_Duplicate,
                                           Source_File    => Index_Filename,
                                           Token_Position => Index_Position,
                                           Token_String   => LexTokenLists.Token_List_To_String (Token_List => Unit));
            end if;

         elsif not (LexTokenManager.Lex_String_Case_Insensitive_Compare
                      (Lex_Str1 => Index_Filename,
                       Lex_Str2 => LexTokenManager.Null_String) = LexTokenManager.Str_Eq) and then
           not (LexTokenManager.Lex_String_Case_Insensitive_Compare
                  (Lex_Str1 => Get_Element (Position => Position).Index_Filename,
                   Lex_Str2 => LexTokenManager.Null_String) = LexTokenManager.Str_Eq) and then
           Eq_Unit_Element_Type (Get_Element (Position => Position), New_Item) then
            --  The same values for the same unit but not at the same
            --  level => duplicate in the index files => raise a
            --  warning.
            Index_Table_P.Output_Error (E              => IndexManager.EW_Duplicate,
                                        Source_File    => Index_Filename,
                                        Token_Position => Index_Position,
                                        Token_String   => LexTokenLists.Token_List_To_String (Token_List => Unit));
         end if;

      end if;
   end Add_Components;

   procedure Get_Components (Required_Unit  : in     LexTokenLists.Lists;
                             Components     :    out IndexManager.Component_Lists;
                             Index_Filename :    out LexTokenManager.Lex_String;
                             Found          :    out Boolean) is
      Position     : Unit_Hash_P.Cursor;
      Unit_Element : Unit_Element_Type;
   begin
      Position := Unit_Hash_P.Find (Container => The_Unit_Hash,
                                    Key       => Unit_Key_Type'(Unit          => Required_Unit,
                                                                Is_Components => True,
                                                                Unit_Types    => ContextManager.InvalidUnit));
      if Position = Unit_Hash_P.No_Element then
         Components     := IndexManager.Component_Lists'(others => LexTokenLists.Null_List);
         Index_Filename := LexTokenManager.Null_String;
         Found          := False;
      else
         Unit_Element   := Get_Element (Position => Position);
         Components     := Unit_Element.Components;
         Index_Filename := Unit_Element.Index_Filename;
         Found          := True;
      end if;
   end Get_Components;

# if SPARK then
begin
   The_Unit_Hash := Unit_Hash_P.Map'
     (others => Unit_Element_Type'(Is_Components   => False,
                                   Source_Filename => LexTokenManager.Null_String,
                                   Components      => IndexManager.Component_Lists'(others => LexTokenLists.Null_List),
                                   Index_Filename  => LexTokenManager.Null_String));
# end if;
end IndexManager.Cache;
