------------------------------------------------------------------------------
--                                                                          --
--                            GNATPP COMPONENTS                             --
--                                                                          --
--                    G N A T P P . P A R A G R A P H S                     --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--                    Copyright (C) 2001-2013, AdaCore                      --
--                                                                          --
-- GNATPP is free software; you can redistribute it  and/or modify it under --
-- terms of the  GNU General Public License as published  by the Free Soft- --
-- ware  Foundation;  either version 2,  or (at your option) any later ver- --
-- sion.  GNATPP is  distributed in the  hope that it will  be  useful, but --
-- WITHOUT ANY WARRANTY; without even the implied warranty of  MERCHANTABI- --
-- LITY 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 GNAT; see file COPYING. If not, --
-- write  to  the Free  Software  Foundation,  59 Temple Place - Suite 330, --
-- Boston,                                                                  --
--                                                                          --
-- GNATPP is maintained by AdaCore (http://www.adacore.com)                 --
--                                                                          --
------------------------------------------------------------------------------

with Table;

with Asis.Clauses;               use Asis.Clauses;
with Asis.Extensions;            use Asis.Extensions;
with Asis.Extensions.Flat_Kinds; use Asis.Extensions.Flat_Kinds;
with Asis.Elements;              use Asis.Elements;
with Asis.Text;                  use Asis.Text;
with Asis.Declarations;          use Asis.Declarations;
with Asis.Definitions;           use Asis.Definitions;
with Asis.Statements;            use Asis.Statements;

with GNATPP.Layout;              use GNATPP.Layout;
with GNATPP.Asis_Utilities;      use GNATPP.Asis_Utilities;
with GNATPP.State;               use GNATPP.State;
with GNATPP.Options;             use GNATPP.Options;
with GNATPP.Stacs;

package body GNATPP.Paragraphs is

   -------------------------------------------------------------------------
   -- Data structures for storing and maintaining the paragraph alignment --
   -------------------------------------------------------------------------

   type Paragraph_Id is new Natural;

   package Paragraph_Alignment is new Table.Table
     (Table_Component_Type => Layout_Info,
      Table_Index_Type     => Paragraph_Id,
      Table_Low_Bound      => 1,
      Table_Initial        => 100,
      Table_Increment      => 100,
      Table_Name           => "");
   --  This table stores the alignment info (colons and assignment signs
   --  positions) for all the paragraphs in the code. For nested declaration or
   --  statement sequences, the alignment info for an inner sequence is stored
   --  AFTER all the alignment info records for outer sequence (this is
   --  because the paragraph layout is computed for the construct which
   --  contains a given sequence and the layout records for all the sequences
   --  are stored in the table. If the sequence contains enclosed sequences,
   --  the alignment info for the inner paragraphs will be computed and stored
   --  only when the general traversing process reach the corresponding
   --  construct being itself a part of outer sequence.

   Next_Paragraph : Paragraph_Id := 0;
   --  Points to the paragraph alignment record to be used now.

   package Paragraph_Stack is new GNATPP.Stacs
     (Element_Type => Paragraph_Id,
      No_Element   => 0);
   --  This stack stores the next paragraph references in case of nested
   --  declaration/statement sequences

   -----------------------------------------
   -- Compute_Alignment_In_Context_Clause --
   -----------------------------------------

   procedure Compute_Alignment_In_Context_Clause
     (Clause_Elements : Asis.Element_List)
   is
      Par_Start         : Natural := 1;
      Par_End           : Natural := 0;

      Old_Line          : Line_Number;

      Use_At            : Natural;
      Next_Alignment    : Layout_Info := Nil_Layout_Info;

      Is_First_Paragraph : Boolean := True;

      procedure Set_Next_Par;
      --  This procedure, starting from the current settings of Par_Start
      --  and Par_End indexes, sets them to point to the beginning and to the
      --  end of the next paragraph. Par_Start = 1 and Par_End = 0 means that
      --  we have to find the first paragraph. Par_Start = 0 means that there
      --  is no paragraph any more.

      procedure Set_Next_Par is
      begin

         if Par_End /= 0 then
            Par_Start := Par_End + 1;
         end if;

         while Par_Start <= Clause_Elements'Last loop

            if Flat_Element_Kind (Clause_Elements (Par_Start)) not in
               Flat_Pragma_Kinds
            then
               exit;
            end if;

            Par_Start := Par_Start + 1;
         end loop;

         if Par_Start > Clause_Elements'Last then
            Par_Start := 0;
            return;
         end if;

         Par_End := Par_Start + 1;
         Old_Line := Element_Span (Clause_Elements (Par_Start)).Last_Line;

         while Par_End <= Clause_Elements'Last loop

            if Flat_Element_Kind (Clause_Elements (Par_End)) in
               Flat_Pragma_Kinds
              or else
               (Element_Span (Clause_Elements (Par_End)).First_Line -
                Old_Line > 1)
            then
               exit;
            end if;

            Old_Line := Element_Span (Clause_Elements (Par_End)).Last_Line;
            Par_End := Par_End + 1;
         end loop;

         Par_End := Par_End - 1;

      end Set_Next_Par;

   begin

      Set_Next_Par;

      while Par_Start > 0 loop

         Compute_Context_Clauses_Alignment
           (Clause_Elements => Clause_Elements (Par_Start .. Par_End),
            Use_Pos         => Use_At);

         Next_Alignment.Pos1 := Use_At;

         Paragraph_Alignment.Append (Next_Alignment);

         if Is_First_Paragraph then
            Next_Paragraph := Paragraph_Alignment.Last - 1;
            --  Then we will add 1 back when moving into a new paragraph
            --  when printing out the aligned constructs
            Is_First_Paragraph := False;
         end if;

         Set_Next_Par;
      end loop;

      In_Paragraph := False;

   end Compute_Alignment_In_Context_Clause;

   -------------------------------------
   -- Compute_Alignment_In_Paragraphs --
   -------------------------------------

   procedure Compute_Alignment_In_Paragraphs (E : Asis.Element) is
      Frame_Kind        : constant Flat_Element_Kinds := Flat_Element_Kind (E);
      All_Constructs    : Element_List_Access;
      Par_Start         : Natural := 1;
      Par_End           : Natural := 0;
      Start_Pos         : Natural;

      Old_Line          : Line_Number;

      Colon_At          : Natural;
      Assign_At         : Natural;
      At_At             : Natural;
      Next_Alignment    : Layout_Info := Nil_Layout_Info;

      Is_First_Paragraph : Boolean := True;

      function Get_Declarations return Element_List;
      function Get_Statements return Element_List;
      --  Returns the list of declarations (statements) from E. Takes care of
      --  different ASIS queries needed to do this for different kinds of E.

      procedure Set_Next_Par;
      --  This procedure, starting from the current settings of Par_Start
      --  and Par_End indexes, sets them to point to the beginning and to the
      --  end of the next declaration or statement paragraph. Par_Start = 1
      --  and Par_End = 0 means that we have to find the first paragraph.
      --  Par_Start = 0 means that there is no paragraph any more.

      function Get_Declarations return Element_List is
      begin

         case Frame_Kind is

            when A_Procedure_Body_Declaration |
                 A_Function_Body_Declaration  |
                 A_Package_Body_Declaration   |
                 A_Task_Body_Declaration      |
                 An_Entry_Body_Declaration    =>

               return Body_Declarative_Items (E, Include_Pragmas => True);

            when A_Package_Declaration         |
                 A_Generic_Package_Declaration =>
               return Visible_Part_Declarative_Items
                        (E, Include_Pragmas => True);

            when A_Protected_Definition =>
               return Visible_Part_Items
                        (E, Include_Pragmas => True);

            when A_Record_Definition |
                 A_Variant           =>

               return Record_Components (E, Include_Pragmas => True);

            when A_Block_Statement =>
               return Block_Declarative_Items (E, Include_Pragmas => True);

            when A_Known_Discriminant_Part =>
               return Discriminants (E);

            when others =>
               return Nil_Element_List;
         end case;

      end Get_Declarations;

      function Get_Statements return Element_List is
      begin

         case Frame_Kind is

            when A_Procedure_Body_Declaration |
                 A_Function_Body_Declaration  |
                 A_Package_Body_Declaration   |
                 A_Task_Body_Declaration      |
                 An_Entry_Body_Declaration    =>

               return Body_Statements (E, Include_Pragmas => True);

            when A_Block_Statement =>

               return Block_Statements (E, Include_Pragmas => True);

            when Flat_Path_Kinds =>

               return Sequence_Of_Statements (E, Include_Pragmas => True);

            when Flat_Loop_Statement =>

               return Loop_Statements (E, Include_Pragmas => True);

            when An_Extended_Return_Statement =>

               return Extended_Return_Statements (E, Include_Pragmas => True);

            when An_Accept_Statement =>

               return Accept_Body_Statements (E, Include_Pragmas => True);

            when An_Exception_Handler =>

               return Handler_Statements (E, Include_Pragmas => True);

            when others =>
               return Nil_Element_List;
         end case;

      end Get_Statements;

      procedure Set_Next_Par is
      begin

         if Par_End /= 0 then
            Par_Start := Par_End + 1;
         end if;

         while Par_Start <= All_Constructs'Last loop

            if Can_Be_Aligned (All_Constructs (Par_Start)) then
               exit;
            end if;

            Par_Start := Par_Start + 1;
         end loop;

         if Par_Start > All_Constructs'Last then
            Par_Start := 0;
            return;
         end if;

         Par_End := Par_Start + 1;
         Old_Line := Element_Span (All_Constructs (Par_Start)).Last_Line;

         while Par_End <= All_Constructs'Last loop

            if not Can_Be_Aligned (All_Constructs (Par_End)) or else
               (Element_Span (All_Constructs (Par_End)).First_Line -
                Old_Line > 1)                                or else
               not Is_Equal (Enclosing_Element (All_Constructs (Par_End)),
                             Enclosing_Element (All_Constructs (Par_End - 1)))
            then
               exit;
            end if;

            Old_Line := Element_Span (All_Constructs (Par_End)).Last_Line;
            Par_End := Par_End + 1;
         end loop;

         Par_End := Par_End - 1;

      end Set_Next_Par;

   begin

      --  For the development period: ensure that E is the right element
      --  for paragraph alignment

      pragma Assert (False
         or else Contains_Declarations (Frame_Kind)
         or else Contains_Statements (Frame_Kind)
         or else Frame_Kind = A_Record_Representation_Clause);

      Start_Pos := Logical_Depth * PP_Indentation + 1;

      if Frame_Kind in A_Loop_Statement .. A_Block_Statement and then
         not Compact_Layout                                  and then
         not Is_Nil (Statement_Identifier (E))
      then
         --  ??? What about labeled frames?
         Start_Pos := Start_Pos + PP_Indentation;
      end if;

      --  Let's start from declarations alignment

      if not Align_Colons_In_Decl or else
         not Contains_Declarations (Frame_Kind)
      then
         goto Statements;
      end if;

      All_Constructs := new Element_List'(Get_Declarations);

      Set_Next_Par;

      while Par_Start > 0 loop

         Compute_Declarations_Alignment
           (Start_Pos  => Start_Pos,
            Elements   => All_Constructs (Par_Start .. Par_End),
            Colon_Pos  => Colon_At,
            Assign_Pos => Assign_At);

         Next_Alignment.Pos1 := Colon_At;
         Next_Alignment.Pos2 := Assign_At;
         --  Should we use update routines with kind protection here???

         Paragraph_Alignment.Append (Next_Alignment);

         if Is_First_Paragraph then
            Next_Paragraph := Paragraph_Alignment.Last - 1;
            --  Then we will add 1 back when moving into a new paragraph
            --  when printing out the aligned constructs
            Is_First_Paragraph := False;
         end if;

         Set_Next_Par;
      end loop;

      if Frame_Kind = A_Package_Declaration         or else
         Frame_Kind = A_Generic_Package_Declaration or else
         Frame_Kind = A_Protected_Definition
      then

         if Frame_Kind = A_Protected_Definition then
            All_Constructs := new Element_List'
              (Private_Part_Items (E, Include_Pragmas => True));
         else
            All_Constructs := new Element_List'
              (Private_Part_Declarative_Items (E, Include_Pragmas => True));
         end if;

         Par_Start := 1;
         Par_End   := 0;

         Set_Next_Par;

         while Par_Start > 0 loop

            Compute_Declarations_Alignment
              (Start_Pos  => Start_Pos,
               Elements   => All_Constructs (Par_Start .. Par_End),
               Colon_Pos  => Colon_At,
               Assign_Pos => Assign_At);

            Next_Alignment.Pos1 := Colon_At;
            Next_Alignment.Pos2 := Assign_At;
            --  Should we use update routines with kind protection here???

            Paragraph_Alignment.Append (Next_Alignment);

            if Is_First_Paragraph then
               Next_Paragraph := Paragraph_Alignment.Last - 1;
               Is_First_Paragraph := False;
            end if;

            Set_Next_Par;
         end loop;

      end if;

      <<Statements>> null;

      if not Align_Asign_In_Stmts or else
         not Contains_Statements (Frame_Kind)
      then
         goto Rec_Rep_Clause;
      end if;

      Next_Alignment := Nil_Layout_Info;
      All_Constructs := new Element_List'(Get_Statements);

      Par_Start := 1;
      Par_End   := 0;

      Set_Next_Par;

      while Par_Start > 0 loop

         Compute_Assign_Statement_Alignment
           (Start_Pos  => Start_Pos,
            Elements   => All_Constructs (Par_Start .. Par_End),
            Assign_Pos => Assign_At);

         Next_Alignment.Pos2 := Assign_At;
         --  Should we use update routines with kind protection here???

         Paragraph_Alignment.Append (Next_Alignment);

         if Is_First_Paragraph then
            Next_Paragraph := Paragraph_Alignment.Last - 1;
            --  Then we will add 1 back when moving into a new paragraph
            --  when printing out the aligned constructs
            Is_First_Paragraph := False;
         end if;

         Set_Next_Par;
      end loop;

      <<Rec_Rep_Clause>> null;

      if not Align_Ats or else
         Frame_Kind /= A_Record_Representation_Clause
      then
         goto Done;
      end if;

      --  The components clauses to align are one level deeper
      Start_Pos := Start_Pos + PP_Indentation;

      if not Compact_Layout then
         Start_Pos := Start_Pos + PP_Indentation;
      end if;

      Next_Alignment := Nil_Layout_Info;
      All_Constructs :=
        new Element_List'(Component_Clauses (E, Include_Pragmas => True));

      Par_Start := 1;
      Par_End   := 0;

      Set_Next_Par;

      while Par_Start > 0 loop

         Compute_Component_Clause_Alignment
           (Start_Pos  => Start_Pos,
            Elements   => All_Constructs (Par_Start .. Par_End),
            At_Pos     => At_At);

         Next_Alignment.Pos1 := At_At;
         --  Should we use update routines with kind protection here???

         Paragraph_Alignment.Append (Next_Alignment);

         if Is_First_Paragraph then
            Next_Paragraph := Paragraph_Alignment.Last - 1;
            --  Then we will add 1 back when moving into a new paragraph
            --  when printing out the aligned constructs
            Is_First_Paragraph := False;
         end if;

         Set_Next_Par;
      end loop;

      <<Done>> Free (All_Constructs);

      In_Paragraph := False;

   end Compute_Alignment_In_Paragraphs;

   ----------------------------
   -- Move_No_Next_Paragraph --
   ----------------------------

   procedure Move_No_Next_Paragraph is
   begin
      Next_Paragraph := Next_Paragraph + 1;
   end Move_No_Next_Paragraph;

   -----------------------
   -- Restore_Paragraph --
   -----------------------

   procedure Restore_Paragraph is
   begin
      In_Paragraph := False;
      Next_Paragraph := Paragraph_Stack.Pop;
   end Restore_Paragraph;

   --------------------
   -- Save_Paragraph --
   --------------------

   procedure Save_Paragraph is
   begin
      In_Paragraph := False;
      Paragraph_Stack.Push (Next_Paragraph);
   end Save_Paragraph;

   -----------------------
   -- Set_New_Paragraph --
   -----------------------

   procedure Set_New_Paragraph is
   begin
      In_Paragraph := True;
      Move_No_Next_Paragraph;

      Colon_In_Paragraph  := Paragraph_Alignment.Table (Next_Paragraph).Pos1;
      Assign_In_Paragraph := Paragraph_Alignment.Table (Next_Paragraph).Pos2;

   end Set_New_Paragraph;

   ----------------------
   -- Set_No_Paragraph --
   ----------------------

   procedure Set_No_Paragraph is
   begin
      In_Paragraph := False;

      Colon_In_Paragraph  := 0;
      Assign_In_Paragraph := 0;
   end Set_No_Paragraph;

end GNATPP.Paragraphs;
