------------------------------------------------------------------------------
--                         Language Server Protocol                         --
--                                                                          --
--                     Copyright (C) 2021-2023, AdaCore                     --
--                                                                          --
-- This 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 3,  or (at your option) any later ver- --
-- sion.  This software is distributed in the hope  that it will be useful, --
-- but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN- --
-- TABILITY 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  this  software;   see  file --
-- COPYING3.  If not, go to http://www.gnu.org/licenses for a complete copy --
-- of the license.                                                          --
------------------------------------------------------------------------------

with LSP.Constants;

package body LSP.Ada_Completions.Filters is

   function Kind
     (Token : Libadalang.Common.Token_Reference)
       return Libadalang.Common.Token_Kind
         is (Libadalang.Common.Kind (Libadalang.Common.Data (Token)));

   ----------------
   -- Initialize --
   ----------------

   procedure Initialize
     (Self  : in out Filter;
      Token : Libadalang.Common.Token_Reference;
      Node  :        Libadalang.Analysis.Ada_Node) is
   begin
      Self.Token := Token;
      Self.Node  := Node;
   end Initialize;

   ------------------
   -- Is_End_Label --
   ------------------

   function Is_End_Label (Self : in out Filter'Class) return Boolean is
      use Libadalang.Common;
   begin
      if not Self.Is_End_Label.Is_Set then
         declare
            Parent : constant Libadalang.Analysis.Ada_Node :=
              (if Self.Node.Is_Null then Self.Node
               else Skip_Dotted_Names (Self.Node.Parent));
            --  Skip the outermost dotted name enclosing Node.Parent, so
            --  that when completing in a situation such as the following:
            --
            --      end Ada.Tex|
            --                 ^ Cursor here
            --
            --  we get the DottedName node's parent rather than just the "Tex"
            --  Id. We want the DottedName parent rather than the Id so as
            --  to get the proper completions (all elements in the "Ada"
            --  namespace).
         begin
            if not Parent.Is_Null
              and then Parent.Kind = Libadalang.Common.Ada_End_Name
            then
               Self.Is_End_Label := LSP.Constants.True;

            elsif Is_End_Token (Self.Token) then
               Self.Is_End_Label := LSP.Constants.True;

            elsif Libadalang.Common.Previous
                (Self.Token, Exclude_Trivia => True) /=  No_Token
              and then Is_End_Token
                (Libadalang.Common.Previous
                   (Self.Token, Exclude_Trivia => True))
            then
               Self.Is_End_Label := LSP.Constants.True;

            else
               Self.Is_End_Label := LSP.Constants.False;

            end if;
         end;
      end if;

      return Self.Is_End_Label.Value;
   end Is_End_Label;

   ------------------------
   -- Is_Numeric_Literal --
   ------------------------

   function Is_Numeric_Literal (Self : in out Filter'Class) return Boolean
   is
      use all type Libadalang.Common.Token_Kind;

      First : Libadalang.Common.Token_Reference := Self.Token;

   begin
      if Self.Is_Numeric_Literal.Is_Set then
         return Self.Is_Numeric_Literal.Value;
      end if;

      --  Let me be pessimistic
      Self.Is_Numeric_Literal := LSP.Constants.False;

      --  An incomplete numeric literal may be represented in LAL as tokens.
      --  Scan tokens backward till the start token of numeric literal.
      case Kind (First) is
         when Ada_Lexing_Failure =>
            --  (Integer|Decimal) (Lexing_Failure)
            --  1_
            --  1.0_
            --  1e0_
            if Libadalang.Common.Text (First) /= "_" then
               return Self.Is_Numeric_Literal.Value;
            end if;

            First := Libadalang.Common.Previous (First);

         when Ada_Dot | Ada_Prep_Line =>
            --  (Integer) (Prep_Line)
            --  2#
            --  2#1
            --  (Integer) (Dot)
            --  1.
            First := Libadalang.Common.Previous (First);

            if Kind (First) /= Ada_Integer then
               return Self.Is_Numeric_Literal.Value;
            end if;

         when Ada_Identifier =>
            --  (Integer|Decimal) (Identifier)
            --  1.0e
            if Libadalang.Common.Text (First) in "e" | "E" then
               First := Libadalang.Common.Previous (First);
            else
               return Self.Is_Numeric_Literal.Value;
            end if;

         when Ada_Plus | Ada_Minus =>
            --  (Integer|Decimal) (Identifier) (Plus|Minus)
            --  1.0e+
            First := Libadalang.Common.Previous (First);

            if Kind (First) = Ada_Identifier
              and then Libadalang.Common.Text (First) in "e" | "E"
            then
               First := Libadalang.Common.Previous (First);
            else
               return Self.Is_Numeric_Literal.Value;
            end if;

         when Ada_Integer | Ada_Decimal =>
            --  A complete (valid) numeric literal
            null;

         when others =>
            return Self.Is_Numeric_Literal.Value;
      end case;

      if Kind (First) not in Ada_Integer | Ada_Decimal then
         return Self.Is_Numeric_Literal.Value;
      end if;

      --  Ok, we have found a numeric literal
      Self.Is_Numeric_Literal := LSP.Constants.True;

      return Self.Is_Numeric_Literal.Value;
   end Is_Numeric_Literal;

   ----------------------
   -- Is_Attribute_Ref --
   ----------------------

   function Is_Attribute_Ref (Self : in out Filter'Class) return Boolean is
   begin
      if not Self.Is_Attribute.Is_Set then
         declare
            use all type Libadalang.Common.Ada_Node_Kind_Type;
            use all type Libadalang.Common.Token_Kind;

            Token_Kind : constant Libadalang.Common.Token_Kind :=
              Kind (Self.Token);
            Parent : Libadalang.Analysis.Ada_Node :=
              (if Self.Node.Is_Null then Self.Node else Self.Node.Parent);
         begin
            --  Get the outermost dotted name of which node is a prefix, so
            --  that when completing in a situation such as the following:
            --
            --      end Ada.Tex|
            --                 ^ Cursor here
            --
            --  we get the DottedName node rather than just the "Tex" BaseId.
            --  We want the DottedName rather than the Id so as to get the
            --  proper completions (all elements in the "Ada" namespace).

            while not Parent.Is_Null
              and then Parent.Kind = Ada_Dotted_Name
            loop
               Parent := Parent.Parent;
            end loop;

            Self.Is_Attribute :=
              (True,
               Token_Kind = Ada_Tick or else
                 (not Parent.Is_Null
                  and then Parent.Kind = Ada_Attribute_Ref));
         end;
      end if;

      return Self.Is_Attribute.Value;
   end Is_Attribute_Ref;

   ---------------
   -- Is_Aspect --
   ---------------

   function Is_Aspect (Self : in out Filter'Class) return Boolean is
      use Libadalang.Common;
   begin
      if not Self.Is_Aspect.Is_Set then

         if Libadalang.Common.Previous
               (Self.Token, Exclude_Trivia => True) = No_Token
         then
            Self.Is_Aspect := (True, False);
            return Self.Is_Aspect.Value;
         end if;

         declare
            Token_Kind : constant Libadalang.Common.Token_Kind :=
              Kind (Libadalang.Common.Previous
                    (Self.Token, Exclude_Trivia => True));
            Parent : Libadalang.Analysis.Ada_Node :=
              (if Self.Node.Is_Null then Self.Node else Self.Node.Parent);
         begin
            --  Get the outermost dotted name of which node is a prefix, so
            --  that when completing in a situation such as the following:
            --
            --      end Ada.Tex|
            --                 ^ Cursor here
            --
            --  we get the DottedName node rather than just the "Tex" BaseId.
            --  We want the DottedName rather than the Id so as to get the
            --  proper completions (all elements in the "Ada" namespace).

            while not Parent.Is_Null
              and then Parent.Kind = Ada_Dotted_Name
            loop
               Parent := Parent.Parent;
            end loop;

            Self.Is_Aspect :=
              (True,
               Token_Kind = Ada_With and then
                 (Self.Node.Kind in Ada_Aspect_Spec_Range
                  or else
                    (not Parent.Is_Null
                     and then Parent.Kind in Ada_Aspect_Assoc_Range)));
         end;
      end if;

      return Self.Is_Aspect.Value;
   end Is_Aspect;

   ------------------
   -- Is_Semicolon --
   ------------------

   function Is_Semicolon (Self : in out Filter'Class) return Boolean is
   begin
      if not Self.Is_Semicolon.Is_Set then
         declare
            use all type Libadalang.Common.Token_Kind;

            Token_Kind : constant Libadalang.Common.Token_Kind :=
              Kind (Self.Token);
         begin
            Self.Is_Semicolon :=
              (True,
               Token_Kind = Ada_Semicolon);
         end;
      end if;

      return Self.Is_Semicolon.Value;
   end Is_Semicolon;

   --------------
   -- Is_Comma --
   --------------

   function Is_Comma (Self : in out Filter'Class) return Boolean is
   begin
      if not Self.Is_Comma.Is_Set then
         declare
            use all type Libadalang.Common.Token_Kind;

            Token_Kind : constant Libadalang.Common.Token_Kind :=
              Kind (Self.Token);
         begin
            Self.Is_Comma :=
              (True,
               Token_Kind = Ada_Comma);
         end;
      end if;

      return Self.Is_Comma.Value;
   end Is_Comma;

   -------------------------
   -- Is_Open_Parenthesis --
   -------------------------

   function Is_Open_Parenthesis
      (Self           : in out Filter'Class;
       Exclude_Trivia : Boolean := True) return Boolean
   is
   begin
      if not Self.Is_Open_Parenthesis.Is_Set then
         declare
            use all type Libadalang.Common.Token_Kind;
            use all type Libadalang.Common.Token_Reference;

            Token_Kind : constant Libadalang.Common.Token_Kind := Self.Token.Data.Kind;
         begin
            Self.Is_Open_Parenthesis := (True, Token_Kind = Ada_Par_Open);

            --  The curent token is not an open parenthesis: check for the previous
            --  one if we are on a trivia.
            if not Self.Is_Open_Parenthesis.Value
              and then (Self.Token.Is_Trivia and then Exclude_Trivia)
            then
               declare
                  Previous_Token :
                    constant Libadalang.Common.Token_Reference :=
                    Self.Token.Previous (Exclude_Trivia => True);
               begin
                  Self.Is_Open_Parenthesis :=
                    (True,
                     Previous_Token /= Libadalang.Common.No_Token
                     and then Previous_Token.Data.Kind =
                       Ada_Par_Open);
               end;
            end if;
         end;
      end if;

      return Self.Is_Open_Parenthesis.Value;
   end Is_Open_Parenthesis;

end LSP.Ada_Completions.Filters;
