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

--  This package contains refactoring tools that allow extracting statements
--  and expressions into new subprograms

with Libadalang.Common; use Libadalang.Common;

private with Ada.Containers;
private with Ada.Containers.Indefinite_Hashed_Maps;
private with Ada.Containers.Indefinite_Ordered_Sets;

package LAL_Refactor.Extract_Subprogram is

   type Available_Subprogram_Kinds_Type is array (Ada_Subp_Kind) of Boolean;

   function Is_Extract_Subprogram_Available
     (Unit                       : Analysis_Unit;
      Section_To_Extract         : Source_Location_Range;
      Available_Subprogram_Kinds : out Available_Subprogram_Kinds_Type)
      return Boolean;
   --  Checks if Section_To_Extract is a set of statements that can be
   --  extracted into a subprogram.

   function Default_Extracted_Subprogram_Name
     (Unit            : Analysis_Unit;
      Location        : Source_Location)
      return Unbounded_String;
   --  Assuming the Location is within a set of statements that can be
   --  extracted, returns a name for the extracted subprogram.
   --  The name will be "Extracted_<_N>" where N is a suffix to avoid
   --  collisions in the declarative part where the statements will be
   --  extracted.

   type Subprogram_Extractor is new Refactoring_Tool with private;

   function Create_Subprogram_Extractor
     (Unit               : Analysis_Unit;
      Section_To_Extract : Source_Location_Range;
      Subprogram_Kind    : Ada_Subp_Kind;
      Subprogram_Name    : Unbounded_String)
      return Subprogram_Extractor;
   --  Subprogram_Extractor constructor.
   --  The section to extract needs to be validated by
   --  Is_Extract_Subprogram_Available.
   --  Subprogram_Kind must be one of the available kinds in the
   --  Available_Subprogram_Kinds parameter of Is_Extract_Subprogram_Available.
   --  Is the inputs are not valid, the Refactor might succeed but generate
   --  invalid code, or a Constrained_Error might be raised to to invalid
   --  node conversions.

   overriding
   function Refactor
     (Self           : Subprogram_Extractor;
      Analysis_Units : access function return Analysis_Unit_Array)
      return Refactoring_Edits;

private

   function "<" (L, R : Defining_Name) return Boolean is
     (L.Sloc_Range < R.Sloc_Range);
   --  < operator used for ordered sets, ordering the Defining_Names by
   --  SLOC

   package Defining_Name_Ordered_Sets is new
     Ada.Containers.Indefinite_Ordered_Sets
       (Element_Type        => Defining_Name,
        "<"                 => LAL_Refactor.Extract_Subprogram."<",
        "="                 => "=");

   subtype Defining_Name_Ordered_Set is Defining_Name_Ordered_Sets.Set;

   function Defining_Name_Hash
     (Node : Defining_Name'Class)
      return Ada.Containers.Hash_Type
   is (Hash (Node.As_Ada_Node));
   --  Casts Node to an Ada_Node and calls Libadalang.Analysis.Hash on it

   package Defining_Name_To_Ada_Mode_Maps is new
     Ada.Containers.Indefinite_Hashed_Maps
       (Key_Type        => Defining_Name'Class,
        Element_Type    => Ada_Mode,
        Hash            => Defining_Name_Hash,
        Equivalent_Keys => "=",
        "="             => "=");

   subtype Parameters_Mode_Map is Defining_Name_To_Ada_Mode_Maps.Map;

   package Defining_Name_To_Basic_Decl_Maps is new
     Ada.Containers.Indefinite_Hashed_Maps
       (Key_Type        => Defining_Name'Class,
        Element_Type    => Basic_Decl'Class,
        Hash            => Defining_Name_Hash,
        Equivalent_Keys => "=",
        "="             => "=");

   subtype Parameters_Type_Map is Defining_Name_To_Basic_Decl_Maps.Map;

   package String_Vectors is new
     Ada.Containers.Indefinite_Vectors (Natural, String);

   subtype String_Vector is String_Vectors.Vector;

   package Stmt_Vectors is new
     Ada.Containers.Indefinite_Vectors (Natural, Stmt'Class);

   subtype Stmt_Vector is Stmt_Vectors.Vector;

   type Subprogram_Extractor is new Refactoring_Tool with
      record
         Unit        : Analysis_Unit;
         Start_Token : Token_Reference;
         End_Token   : Token_Reference;
         Start_Stmt  : Stmt;
         End_Stmt    : Stmt;
         Name        : Unbounded_String;
         Kind        : Ada_Subp_Kind;
      end record;

   type Extract_Subprogram_Strategy is abstract tagged
      record
         Unit        : Analysis_Unit;
         Start_Token : Token_Reference;
         End_Token   : Token_Reference;
         Start_Stmt  : Stmt;
         End_Stmt    : Stmt;
         Name        : Unbounded_String;
      end record;

   function Extract
     (Self : Extract_Subprogram_Strategy'Class)
      return Refactoring_Edits;
   --  Extracts a subprogram

   function Get_Local_Declarations
     (Self : Extract_Subprogram_Strategy)
      return Defining_Name_Ordered_Set;
   --  Find all object declaration that are candidates to be parameters of
   --  the extracted subprogram

   function Get_Parameters_To_Extract
     (Self       : Extract_Subprogram_Strategy;
      Candidates : Defining_Name_Ordered_Set;
      Statements : Stmt_Vector)
      return Defining_Name_Ordered_Set is abstract;
   --  Filters Candidates by checking which ones are references in
   --  Statements. These will need to be parameters of the extracted function.
   --  Additional checks might be needed, depending what kind of subprogram
   --  we're extracting.

   function Get_Parameters_Mode
     (Self       : Extract_Subprogram_Strategy;
      Parameters : Defining_Name_Ordered_Set;
      Statements : Stmt_Vector)
      return Parameters_Mode_Map is abstract;
   --  For each Parameter of Parameters, compute its mode based on its usage in
   --  Statements, i.e., if it is read or written.

   function Get_Parameters_Type
     (Self       : Extract_Subprogram_Strategy;
      Parameters : Defining_Name_Ordered_Set)
      return Parameters_Type_Map;
   --  For each Parameter of Parameters, compute its type

   function Build_Extracted_Subprogram_Parameters
     (Self            : Extract_Subprogram_Strategy;
      Parameters      : Defining_Name_Ordered_Set;
      Parameters_Mode : Parameters_Mode_Map;
      Parameters_Type : Parameters_Type_Map)
      return String_Vector;
   --  Returns a String_Vector with all the Param_Specs of the extracted
   --  subprogram.

   function Build_Extracted_Subprogram_Statements
     (Self : Extract_Subprogram_Strategy)
      return String_Vector is abstract;
   --  Returns a String_Vector with all Stmts of the extracted subprogram

   function Build_Extracted_Subprogram
     (Self        : Extract_Subprogram_Strategy;
      Params      : String_Vector;
      Statements  : String_Vector;
      Offset      : Natural;
      Indentation : Natural := 3)
      return String is abstract;
   --  Returns the Ada code of the extracted subprogram

   function Build_Extracted_Subprogram_Call
     (Self       : Extract_Subprogram_Strategy;
      Name       : String;
      Parameters : String_Vector)
      return String is abstract;
   --  Returns the Ada code of the extracted subprogram call

   type Procedure_Extractor is new Extract_Subprogram_Strategy with
     null record;

   overriding
   function Get_Parameters_To_Extract
     (Self       : Procedure_Extractor;
      Candidates : Defining_Name_Ordered_Set;
      Statements : Stmt_Vector)
      return Defining_Name_Ordered_Set;

   overriding
   function Get_Parameters_Mode
     (Self       : Procedure_Extractor;
      Parameters : Defining_Name_Ordered_Set;
      Statements : Stmt_Vector)
      return Parameters_Mode_Map;

   overriding
   function Build_Extracted_Subprogram_Statements
     (Self : Procedure_Extractor)
      return String_Vector;

   overriding
   function Build_Extracted_Subprogram
     (Self        : Procedure_Extractor;
      Params      : String_Vector;
      Statements  : String_Vector;
      Offset      : Natural;
      Indentation : Natural := 3)
      return String;

   overriding
   function Build_Extracted_Subprogram_Call
     (Self       : Procedure_Extractor;
      Name       : String;
      Parameters : String_Vector)
      return String;

   type Function_Extractor is new Extract_Subprogram_Strategy with
     null record;

   overriding
   function Get_Parameters_To_Extract
     (Self       : Function_Extractor;
      Candidates : Defining_Name_Ordered_Set;
      Statements : Stmt_Vector)
      return Defining_Name_Ordered_Set;

   overriding
   function Get_Parameters_Mode
     (Self       : Function_Extractor;
      Parameters : Defining_Name_Ordered_Set;
      Statements : Stmt_Vector)
      return Parameters_Mode_Map;

   overriding
   function Build_Extracted_Subprogram_Statements
     (Self : Function_Extractor)
      return String_Vector;

   overriding
   function Build_Extracted_Subprogram
     (Self        : Function_Extractor;
      Params      : String_Vector;
      Statements  : String_Vector;
      Offset      : Natural;
      Indentation : Natural := 3)
      return String;

   overriding
   function Build_Extracted_Subprogram_Call
     (Self       : Function_Extractor;
      Name       : String;
      Parameters : String_Vector)
      return String;

end LAL_Refactor.Extract_Subprogram;
