--
--  Copyright (C) 2022-2024, AdaCore
--
--  SPDX-License-Identifier: Apache-2.0 WITH LLVM-Exception
--

with Ada.Containers.Indefinite_Ordered_Sets;
with Ada.Iterator_Interfaces;

with GNATCOLL.Directed_Graph;
with GNATCOLL.OS.FS;

with GPR2.Build.Actions;
with GPR2.Build.Artifacts;
with GPR2.Build.View_Db;
with GPR2.Log;
with GPR2.Path_Name;
with GPR2.Project.View;
with GPR2.View_Ids;

private with Ada.Containers.Hashed_Maps;
private with Ada.Containers.Indefinite_Ordered_Maps;

limited private with GPR2.Tree_Internal;

package GPR2.Build.Tree_Db is

   type Object is tagged limited private;
   type Object_Access is access all Object;

   Undefined : constant Object;

   package DG renames GNATCOLL.Directed_Graph;

   function Is_Defined (Self : Object) return Boolean;

   --  CREATING/UNLOADING/REFRESHING THE TREE DATABASE:

   procedure Check_Tree (Self : in out Object)
     with Pre => Self.Is_Defined;
   --  Called when the project tree is updated to check that internal
   --  database structure is updated accordingly (proper view databases
   --  added or removed when appropriate).

   procedure Unload (Self : in out Object)
     with Post => not Self.Is_Defined;

   procedure Refresh
     (Self     : in out Object;
      Option   : Source_Info_Option;
      Messages : out GPR2.Log.Object)
     with Pre => Self.Is_Defined;

   function Source_Option (Self : Object) return Optional_Source_Info_Option;

   --  VIEW DATABASE LOOKUP:

   function View_Database
     (Self : Object;
      View : GPR2.Project.View.Object) return Build.View_Db.Object
     with Pre => Self.Is_Defined
                   and then View.Is_Defined
                   and then View.Kind not in K_Aggregate | K_Configuration,
          Inline;

   function View_Database
     (Self : Object;
      View : GPR2.View_Ids.View_Id) return Build.View_Db.Object
     with Pre => Self.Is_Defined,
          Inline;

   function Ref (Self : Object) return access Object
     with Pre => Self.Is_Defined;

   --  BUILD GRAPH SUPPORT

   procedure Add_Action
     (Self     : in out Object;
      Action   : in out Actions.Object'Class;
      Messages : in out GPR2.Log.Object)
     with Pre =>
       Self.Is_Defined
       and then not Self.Has_Action (Action.UID)
       and then not Messages.Has_Error;

   function Has_Action
     (Self : Object;
      Id   : Actions.Action_Id'Class) return Boolean
     with Pre => Self.Is_Defined;

   function Action
     (Self : Object;
      Id   : Actions.Action_Id'Class) return Actions.Object'Class
     with Pre => Self.Is_Defined;
   --  ???

   procedure Add_Artifact
     (Self     : in out Object;
      Artifact : Artifacts.Object'Class)
     with Pre => Self.Is_Defined;

   function Has_Artifact
     (Self     : Object;
      Artifact : Artifacts.Object'Class) return Boolean;

   procedure Add_Input
     (Self     : in out Object;
      Action   : Actions.Action_Id'Class;
      Artifact : Artifacts.Object'Class;
      Explicit : Boolean)
     with Pre => Self.Is_Defined
                   and then Self.Has_Action (Action)
                   and then Artifact.Is_Defined;

   procedure Add_Output
     (Self     : in out Object;
      Action   : Actions.Action_Id'Class;
      Artifact : Artifacts.Object'Class;
      Messages : in out GPR2.Log.Object)
     with Pre => Self.Is_Defined
                   and then Self.Has_Action (Action)
                   and then Artifact.Is_Defined;

   --  ACTION MANAGEMENT

   function Db_Filename_Path
     (Self   : in out Object;
      Action : Actions.Action_Id'Class) return Path_Name.Object;

   function Actions_Graph_Access
     (Self : in out Object) return access DG.Directed_Graph;
   --  ???

   function Action_Id
     (Self : in out Object; Node : DG.Node_Id) return Actions.Action_Id'Class;
   --  ???

   procedure Propagate_Actions (Self : Object);
   --  ???

   ----------------------------
   -- Iteration on artifacts --
   ----------------------------

   type Artifact_Cursor is private;

   No_Artifact_Element : constant Artifact_Cursor;

   function Has_Element (Position : Artifact_Cursor) return Boolean;

   package Artifact_Iterators is new Ada.Iterator_Interfaces
     (Artifact_Cursor, Has_Element);

   type Artifacts_List (<>) is tagged private
     with Default_Iterator  => Artifact_Iterate,
          Iterator_Element  => Artifacts.Object'Class,
          Constant_Indexing => Constant_Artifact_Reference;

   function Artifact_Iterate
     (List : Artifacts_List) return Artifact_Iterators.Forward_Iterator'Class;

   type Constant_Artifact_Reference_Type
     (Element : not null access constant Artifacts.Object'Class) is private
     with Implicit_Dereference => Element;

   function Constant_Artifact_Reference
     (Iterator : aliased Artifacts_List;
      Pos      : Artifact_Cursor)
      return Constant_Artifact_Reference_Type;

   package Artifact_Sets is new Ada.Containers.Indefinite_Ordered_Sets
     (GPR2.Build.Artifacts.Object'Class,
      GPR2.Build.Artifacts.Less, GPR2.Build.Artifacts."=");

   type Action_Cursor is private;
   No_Action_Element : constant Action_Cursor;

   function Has_Element (Position : Action_Cursor) return Boolean;

   package Action_Iterators is new Ada.Iterator_Interfaces
     (Action_Cursor, Has_Element);

   type Actions_List is tagged private
     with Variable_Indexing => Action_Reference,
          Constant_Indexing => Constant_Action_Reference,
          Default_Iterator  => Action_Iterate,
          Iterator_Element  => Actions.Object'Class;

   function Action_Iterate
     (List : Actions_List) return Action_Iterators.Forward_Iterator'Class;

   type Action_Reference_Type
     (Element : not null access Actions.Object'Class) is private
     with Implicit_Dereference => Element;

   function Action_Id_To_Reference
     (Self : in out Object;
      Id   : Actions.Action_Id'Class) return Action_Reference_Type
     with Pre => Self.Is_Defined;

   function Action_Reference
     (Iterator : aliased in out Actions_List;
      Pos      : Action_Cursor) return Action_Reference_Type;

   type Constant_Action_Reference_Type
     (Element : not null access constant Actions.Object'Class) is private
     with Implicit_Dereference => Element;

   function Constant_Action_Reference
     (Iterator : aliased Actions_List;
      Pos      : Action_Cursor)
      return Constant_Action_Reference_Type;

   function All_Actions (Self : Object) return Actions_List'Class;

   function Inputs
     (Self   : Object;
      Action : Actions.Action_Id'Class) return Artifacts_List'Class;

   function Outputs
     (Self   : Object;
      Action : Actions.Action_Id'Class) return Artifacts_List'Class;

   function Successors
     (Self     : Object;
      Artifact : Artifacts.Object'Class) return Actions_List'Class;

   function Predecessor
     (Self     : Object;
      Artifact : Artifacts.Object'Class) return Actions.Object'Class;

   -------------------------
   -- Temp files handling --
   -------------------------

   type Temp_File (Path_Len : Natural) is record
      FD   : GNATCOLL.OS.FS.File_Descriptor;
      Path : Filename_Type (1 .. Path_Len);
   end record;

   function Get_Or_Create_Temp_File
     (Self     : Object;
      For_View : GPR2.Project.View.Object;
      Purpose  : Simple_Name) return Temp_File
   with Pre => For_View.Kind in With_Object_Dir_Kind;

   procedure Clear_Temp_Files (Self : Object);
   --  Make sure all temp files are cleaned up

private

   use all type DG.Node_Id;

   function Hash (A : Artifacts.Object'Class) return Ada.Containers.Hash_Type
   is (A.Hash);

   package Build_DB_Maps is new Ada.Containers.Hashed_Maps
     (GPR2.View_Ids.View_Id, Build.View_Db.Object,
      GPR2.View_Ids.Hash, GPR2.View_Ids."=", Build.View_Db."=");

   package Action_Maps is new Ada.Containers.Indefinite_Ordered_Maps
     (GPR2.Build.Actions.Action_Id'Class, GPR2.Build.Actions.Object'Class,
      GPR2.Build.Actions.Less, GPR2.Build.Actions."=");

   package Action_Sets is new Ada.Containers.Indefinite_Ordered_Sets
     (GPR2.Build.Actions.Action_Id'Class,
      GPR2.Build.Actions.Less, GPR2.Build.Actions."=");

   package Action_Artifacts_Maps is new Ada.Containers.Indefinite_Ordered_Maps
     (GPR2.Build.Actions.Action_Id'Class, Artifact_Sets.Set,
      GPR2.Build.Actions.Less, Artifact_Sets."=");

   package Artifact_Actions_Maps is new Ada.Containers.Indefinite_Hashed_Maps
     (GPR2.Build.Artifacts.Object'Class, Action_Sets.Set, Hash,
      GPR2.Build.Artifacts."=", Action_Sets."=");

   package Artifact_Action_Maps is new Ada.Containers.Indefinite_Hashed_Maps
     (GPR2.Build.Artifacts.Object'Class, Actions.Action_Id'Class, Hash,
      GPR2.Build.Artifacts."=", Actions."=");

   package Action_Node_Maps is new Ada.Containers.Indefinite_Ordered_Maps
     (GPR2.Build.Actions.Action_Id'Class, DG.Node_Id, GPR2.Build.Actions.Less);

   package Node_Action_Maps is new Ada.Containers.Indefinite_Ordered_Maps
     (DG.Node_Id, GPR2.Build.Actions.Action_Id'Class,
      "=" => GPR2.Build.Actions."=");

   type Object is tagged limited record
   --  Options:
      Src_Option      : Optional_Source_Info_Option := No_Source;

      Self            : access Object;
      --  Handy self-reference

      Tree            : access GPR2.Tree_Internal.Object;
      --  The project tree

      Build_Dbs       : Build_DB_Maps.Map;
      --  Distributed database objects sources from the views

      Actions         : Action_Maps.Map;
      Artifacts       : Artifact_Sets.Set;
      New_Actions     : Action_Sets.Set;

      Inputs          : Action_Artifacts_Maps.Map;
      --  Explicit input(s) in the command line
      Implicit_Inputs : Action_Artifacts_Maps.Map;
      --  Implicit input(s): included by the explicit ones.
      Outputs         : Action_Artifacts_Maps.Map;
      --  Artifacts produced by a given action

      Successors      : Artifact_Actions_Maps.Map;
      Predecessor     : Artifact_Action_Maps.Map;

      Actions_Graph   : aliased GNATCOLL.Directed_Graph.Directed_Graph;
      Node_To_Action  : Node_Action_Maps.Map;
      Action_To_Node  : Action_Node_Maps.Map;
   end record;

   procedure Create
     (Self : in out Object;
      Tree : GPR2.Tree_Internal.Object)
     with Pre => not Self.Is_Defined;

   Undefined : constant Object := (others => <>);

   function Is_Defined (Self : Object) return Boolean is
     (Self.Self /= null);

   function Ref (Self : Object) return access Object is
     (Self.Self);

   function Source_Option (Self : Object) return Optional_Source_Info_Option is
     (Self.Src_Option);

   function View_Database
     (Self : Object; View : GPR2.Project.View.Object)
      return Build.View_Db.Object
   is (Self.Build_Dbs.Element (View.Id));

   function View_Database
     (Self : Object;
      View : GPR2.View_Ids.View_Id) return Build.View_Db.Object
   is (Self.Build_Dbs.Element (View));

   function Has_Action
     (Self : Object;
      Id   : Actions.Action_Id'Class) return Boolean
   is (Self.Actions.Contains (Id));

   function Action
     (Self : Object;
      Id   : Actions.Action_Id'Class) return Actions.Object'Class
   is (Self.Actions (Id));

   function Has_Artifact
     (Self     : Object;
      Artifact : Artifacts.Object'Class) return Boolean
   is (Self.Artifacts.Contains (Artifact));

   type Artifact_List_Kind is (Global_List,
                               Explicit_Inputs,
                               Implicit_Inputs,
                               Inputs,
                               Outputs);

   type Artifact_Cursor is record
      Pos     : Artifact_Sets.Cursor;
      --  Cursor to the artifact object
      Map_Pos : Action_Artifacts_Maps.Cursor;
      --  Cursor to the action->artifacts map element that contains Pos
      Current : Artifact_List_Kind := Global_List;
      --  If Kind is Inputs, this field is used to know if we're currently on
      --  the explicit or implicit list
   end record;

   No_Artifact_Element : constant Artifact_Cursor := (others => <>);

   function Has_Element (Position : Artifact_Cursor) return Boolean
   is (Artifact_Sets.Has_Element (Position.Pos));

   type Constant_Artifact_Reference_Type
     (Element : not null access constant Artifacts.Object'Class)
   is record
      Ref : Artifact_Sets.Constant_Reference_Type (Element);
   end record;

   type Artifacts_List (Kind : Artifact_List_Kind) is tagged record
      Db : access Object;

      case Kind is
         when Global_List =>
            null;
         when others =>
            Action : Action_Maps.Cursor;
      end case;
   end record;

   type Action_Cursor is record
      Pos     : Action_Maps.Cursor;
      Set_Pos : Action_Sets.Cursor;
   end record;

   No_Action_Element : constant Action_Cursor := (others => <>);

   function Has_Element (Position : Action_Cursor) return Boolean
   is (Action_Maps.Has_Element (Position.Pos));

   type Action_Reference_Type
     (Element : not null access Actions.Object'Class)
   is record
      Ref : Action_Maps.Reference_Type (Element);
   end record;

   type Constant_Action_Reference_Type
     (Element : not null access constant Actions.Object'Class)
   is record
      Ref : Action_Maps.Constant_Reference_Type (Element);
   end record;

   type Action_List_Kind is (Global_List,
                             Successors);

   type Actions_List is tagged record
      Kind     : Action_List_Kind := Global_List;
      Db       : access Object;
      Artifact : Artifact_Sets.Cursor := Artifact_Sets.No_Element;
   end record;

   function All_Actions (Self : Object) return Actions_List'Class is
     (Actions_List'(Kind     => Global_List,
                    Db       => Self.Self,
                    Artifact => Artifact_Sets.No_Element));

   function Inputs
     (Self : Object;
      Action : Actions.Action_Id'Class) return Artifacts_List'Class
   is (Artifacts_List'
         (Kind   => Inputs,
          Db     => Self.Self,
          Action => Self.Actions.Find (Action)));

   function Outputs
     (Self   : Object;
      Action : Actions.Action_Id'Class) return Artifacts_List'Class
   is (Artifacts_List'
         (Kind   => Outputs,
          Db     => Self.Self,
          Action => Self.Actions.Find (Action)));

   function Predecessor
     (Self     : Object;
      Artifact : Artifacts.Object'Class) return Actions.Object'Class
   is (Self.Actions (Self.Predecessor (Artifact)));

   function Successors
     (Self     : Object;
      Artifact : Artifacts.Object'Class) return Actions_List'Class
   is (Actions_List'
         (Kind     => Successors,
          Db       => Self.Self,
          Artifact => Self.Artifacts.Find (Artifact)));

end GPR2.Build.Tree_Db;
