--  Abstract:
--
--  See spec
--
--  Copyright (C) 2009, 2014-2015, 2017 - 2023 Free Software Foundation, Inc.
--
--  This file is part of the WisiToken package.
--
--  The WisiToken package is free software; you can redistribute it
--  and/or modify it under the 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 WisiToken package 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 WisiToken package;
--  see file GPL.txt. If not, write to the Free Software Foundation,
--  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
--
--  As a special exception, if other files instantiate generics from
--  this unit, or you link this unit with other files to produce an
--  executable, this unit does not by itself cause the resulting
--  executable to be covered by the GNU General Public License. This
--  exception does not however invalidate any other reasons why the
--  executable file might be covered by the GNU Public License.
-------------------------------------------------------------------------------

with Ada.Characters.Handling;
with Ada.Streams.Stream_IO;
with Ada.Strings.Fixed;
package body WisiToken is

   function Padded_Image (Item : in Token_ID; Desc : in Descriptor) return String
   is begin
      return Ada.Strings.Fixed.Head
        (Desc.Image (Item).all,
         (if Item in Desc.First_Terminal .. Desc.Last_Terminal
          then Desc.Terminal_Image_Width
          else Desc.Image_Width));
   end Padded_Image;

   function Image (Item : in Token_ID; Desc : in Descriptor) return String
   is begin
      return (if Item = Invalid_Token_ID then "-" else Desc.Image (Item).all);
   end Image;

   procedure Put_Tokens (Descriptor : in WisiToken.Descriptor)
   is
      use Ada.Text_IO;
   begin
      for I in Token_ID'First .. Descriptor.Last_Nonterminal loop
         Put_Line (Token_ID'Image (I) & " => " & Descriptor.Image (I).all);
      end loop;
   end Put_Tokens;

   function Find_ID (Descriptor : in WisiToken.Descriptor; Name : in String) return Token_ID
   is begin
      for I in Descriptor.Image'Range loop
         if Descriptor.Image (I).all = Name then
            return I;
         end if;
      end loop;
      raise SAL.Programmer_Error with "token name '" & Name & "' not found in descriptor.image";
   end Find_ID;

   procedure To_Vector (Item : in Token_ID_Array; Vector : in out Token_ID_Arrays.Vector)
   is
      J : Integer := Vector.First_Index;
   begin
      for ID of Item loop
         Vector.Replace_Element (J, ID);
         J := J + 1;
      end loop;
   end To_Vector;

   function To_Vector (Item : in Token_ID_Array) return Token_ID_Arrays.Vector
   is begin
      return Result : Token_ID_Arrays.Vector do
         Result.Set_First_Last (Item'First, Item'Last);
         for I in Item'Range loop
            Result (I) := Item (I);
         end loop;
      end return;
   end To_Vector;

   function To_Array (Item : in Token_ID_Arrays.Vector) return Token_ID_Array
   is begin
      return Result : Token_ID_Array (Item.First_Index .. Item.Last_Index) do
         for I in Result'Range loop
            Result (I) := Item (I);
         end loop;
      end return;
   end To_Array;

   function Shared_Prefix (A, B : in Token_ID_Arrays.Vector) return Natural
   is
      use all type Ada.Containers.Count_Type;
      I : Natural := A.First_Index;
      J : Natural := B.First_Index;
   begin
      if A.Length = 0 or B.Length = 0 then
         return 0;
      end if;

      loop
         exit when A (I) /= B (I) or I = A.Last_Index or J = B.Last_Index;
         I := I + 1;
         J := J + 1;
      end loop;
      return I - 1;
   end Shared_Prefix;

   function "&" (Left : in Token_ID_Set; Right : in Token_ID) return Token_ID_Set
   is begin
      return Result : Token_ID_Set := Left do
         Result (Right) := True;
      end return;
   end "&";

   function To_Token_ID_Set (First, Last : in Token_ID; Item : in Token_ID_Array) return Token_ID_Set
   is begin
      return Result : Token_ID_Set := (First .. Last => False)
      do
         for ID of Item loop
            Result (ID) := True;
         end loop;
      end return;
   end To_Token_ID_Set;

   procedure To_Set (Item : in Token_ID_Arrays.Vector; Set : out Token_ID_Set)
   is begin
      for ID of Item loop
         Set (ID) := True;
      end loop;
   end To_Set;

   function To_Array (Item : in Token_ID_Set) return Token_ID_Arrays.Vector
   is begin
      return Result : Token_ID_Arrays.Vector do
         for ID in Item'Range loop
            if Item (ID) then
               Result.Append (ID);
            end if;
         end loop;
      end return;
   end To_Array;

   function Any (Item : in Token_ID_Set) return Boolean
   is begin
      for I in Item'Range loop
         if Item (I) then
            return True;
         end if;
      end loop;
      return False;
   end Any;

   function Count (Item : in Token_ID_Set) return Integer
   is
      Result : Integer := 0;
   begin
      for I in Item'Range loop
         if Item (I) then
            Result := Result + 1;
         end if;
      end loop;
      return Result;
   end Count;

   function Image
     (Item      : in Token_ID_Set;
      Desc      : in Descriptor;
      Max_Count : in Integer := Integer'Last;
      Inverted  : in Boolean := False)
     return String
   is
      use Ada.Strings.Unbounded;
      Result     : Unbounded_String;
      Need_Comma : Boolean := False;
      Count      : Integer := 0;

      function Include (Item : in Boolean) return Boolean
      is begin
         if not Inverted then
            return Item;

         else
            return not Item;
         end if;
      end Include;

   begin
      for I in Item'Range loop
         if Include (Item (I)) then
            if Need_Comma then
               Result := Result & ", ";
            end if;
            Result     := Result & Image (I, Desc);
            Need_Comma := True;
            Count := Count + 1;
            if Count = Max_Count then
               return To_String (Result);
            end if;
         end if;
      end loop;
      return To_String (Result);
   end Image;

   function Image (Item : in Production_ID) return String
   is begin
      return '(' & Trimmed_Image (Item.LHS) & ',' & Natural'Image (Item.RHS) & ')';
   end Image;

   function Image (Item : in Production_ID; Descriptor : in WisiToken.Descriptor) return String
   is begin
      return Image (Item.LHS, Descriptor) & '_' & Trimmed_Image (Item.RHS);
   end Image;

   function Trimmed_Image (Item : in Production_ID) return String
   is begin
      return Trimmed_Image (Item.LHS) & '.' & Trimmed_Image (Item.RHS);
   end Trimmed_Image;

   function Padded_Image (Item : in Production_ID; Width : in Integer) return String
   is
      use Ada.Strings.Fixed;
   begin
      return Result : String (1 .. Width) do
         Move (Trimmed_Image (Item), Result, Justify => Ada.Strings.Right);
      end return;
   end Padded_Image;

   function To_Vector (Item : in Production_ID_Array) return Production_ID_Arrays.Vector
   is begin
      return Result : Production_ID_Arrays.Vector do
         for I of Item loop
            Result.Append (I);
         end loop;
      end return;
   end To_Vector;

   function Slice (Item : in Token_Array_Token_Set; I : in Token_ID) return Token_ID_Set
   is
      Result : Token_ID_Set := (Item'First (2) .. Item'Last (2) => False);
   begin
      for J in Result'Range loop
         Result (J) := Item (I, J);
      end loop;
      return Result;
   end Slice;

   function Any (Item : in Token_Array_Token_Set; I : in Token_ID) return Boolean
   is begin
      for J in Item'Range (2) loop
         if Item (I, J) then
            return True;
         end if;
      end loop;
      return False;
   end Any;

   function Any (Item : in Token_Array_Token_Set) return Boolean
   is begin
      for I in Item'Range (1) loop
         for J in Item'Range (2) loop
            if Item (I, J) then
               return True;
            end if;
         end loop;
      end loop;
      return False;
   end Any;

   procedure Or_Slice (Item : in out Token_Array_Token_Set; I : in Token_ID; Value : in Token_ID_Set)
   is begin
      for J in Item'Range (2) loop
         Item (I, J) := Item (I, J) or Value (J);
      end loop;
   end Or_Slice;

   procedure Put (Descriptor : in WisiToken.Descriptor; Item : in Token_Array_Token_Set)
   is
      use Ada.Text_IO;
      Paren_Done : Boolean := False;
   begin
      if not Any (Item) then
         Put_Line ("(others => (others => False))");
      else
         Put ("(");
         for I in Item'Range (1) loop
            if Any (Item, I) then
               Put_Line (" " & Image (I, Descriptor) & " =>");
               Put ("  (");
               Paren_Done := False;
               for J in Item'Range (2) loop
                  if Item (I, J) then
                     if Paren_Done then
                        Put_Line (" |");
                        Put ("   " & Image (J, Descriptor));
                     else
                        Paren_Done := True;
                        Put (Image (J, Descriptor));
                     end if;
                  end if;
               end loop;
               if Paren_Done then
                  Put_Line (" => True,");
                  Put_Line ("   others => False)");
               else
                  Put_Line (" others => False),");
               end if;
            end if;
         end loop;
         Put_Line ((if Paren_Done then " " else "") & "others => (others => False))");
      end if;
   end Put;

   procedure Report_Memory (Trace : in out WisiToken.Trace'Class; Prefix : in Boolean)
   is
      use GNATCOLL.Memory;
      Memory_Use : constant Watermark_Info := Get_Ada_Allocations;

      procedure Trace_Put_Line (S : in String)
      is begin
         Trace.Put (S, Prefix);
         Trace.New_Line;
      end Trace_Put_Line;

      procedure Trace_Put (S : in String)
      is begin
         Trace.Put (S, Prefix);
      end Trace_Put;

      procedure Trace_Dump is new Redirectable_Dump
        (Put_Line => Trace_Put_Line,
         Put      => Trace_Put);
   begin
      if WisiToken.Trace_Memory > 1 then
         Trace_Dump (4 * Trace_Memory, Report => Memory_Usage);
      end if;

      Trace.Put
        ("(message ""memory: high" &
           Byte_Count'Image (Memory_Use.High) &
           " current" &
           Byte_Count'Image (Memory_Use.Current - Memory_Baseline) & """)",
         Prefix => False);
      Trace.New_Line;
   end Report_Memory;

   function Image (Item : in Buffer_Region) return String
   is begin
      return "(" & Trimmed_Image (Item.First) & " ." & Base_Buffer_Pos'Image (Item.Last) & ")";
   end Image;

   function "and" (Left, Right : in Buffer_Region) return Buffer_Region
   is begin
      return (Buffer_Pos'Min (Left.First, Right.First), Buffer_Pos'Max (Left.Last, Right.Last));
   end "and";

   function Contains
     (Outer, Inner   : in Buffer_Region;
      First_Boundary : in Boundary := Inclusive;
      Last_Boundary  : in Boundary := Inclusive)
     return Boolean
   is
      Result : Boolean;
   begin
      case First_Boundary is
      when Inclusive =>
         Result := Outer.First <= Inner.First;
      when Exclusive =>
         Result := Outer.First < Inner.First;
      end case;

      case Last_Boundary is
      when Inclusive =>
         Result := @ and Outer.Last >= Inner.Last;
      when Exclusive =>
         Result := @ and Outer.Last > Inner.Last;
      end case;
      return Result;
   end Contains;

   function Overlaps (A, B : in Buffer_Region) return Boolean
   is begin
      if Length (A) > 0 and Length (B) > 0 then
         return Contains (B, A.First) or Contains (B, A.Last) or Contains (A, B.First) or Contains (A, B.Last);
      else
         return False;
      end if;
   end Overlaps;

   function Trimmed_Image (Item : in Base_Line_Number_Type) return String
   is
      function Base_Trimmed_Image is new SAL.Gen_Trimmed_Image (Line_Number_Type);
   begin
      if Item = Invalid_Line_Number then
         return "-";
      else
         return Base_Trimmed_Image (Item);
      end if;
   end Trimmed_Image;

   function Image (Item : in Line_Region) return String
   is begin
      return "(" & Trimmed_Image (Item.First) & "," & Item.Last'Image & ")";
   end Image;

   function "+" (Left : in Line_Region; Right : in Base_Line_Number_Type) return Line_Region
   is begin
      return (Left.First + Right, Left.Last + Right);
   end "+";

   procedure Enable_Trace (Config : in String)
   is
      use Ada.Characters.Handling;
      use Ada.Strings.Fixed;
      Name_First : Integer := Config'First;
      Name_Last  : Integer;

      Value_First : Integer;
      Value_Last  : Integer;
   begin
      loop
         Name_Last := Index (Config, "=", Name_First);
         exit when Name_Last = 0;

         Value_First := Name_Last + 1;
         Name_Last   := Name_Last - 1;
         Value_Last  := Index (Config, " ", Value_First);
         if Value_Last = 0 then
            Value_Last := Config'Last;
         end if;
         declare
            Name : constant String := To_Lower (Config (Name_First .. Name_Last));

            function Get_Value return Integer
            is begin
               return Integer'Value (Config (Value_First .. Value_Last));
            exception
            when Constraint_Error =>
               raise User_Error with "expecting integer trace value, found '" &
                 Config (Value_First .. Value_Last) & "'";
            end Get_Value;

            Value : constant Integer := Get_Value;
         begin
            --  Trace var alphabetical order. When modify this, also modify
            --  Enable_Trace_Help.
            if Name = "debug" then
               Debug_Mode := Value > 0;
            elsif Name = "action" then
               Trace_Action := Value;
            elsif Name = "conflicts" or Name = "generate_conflicts" then
               Trace_Generate_Conflicts := Value;
            elsif Name = "ebnf" or Name = "generate_ebnf" then
               Trace_Generate_EBNF := Value;
            elsif Name = "generate" then
               Trace_Generate := Value;
            elsif Name = "minimal_complete" or Name = "generate_minimal_complete" then
               Trace_Generate_Minimal_Complete := Value;
            elsif Name = "table" or Name = "generate_table" then
               Trace_Generate_Table := Value;
            elsif Name = "incremental" or Name = "incremental_parse" then
               Trace_Incremental_Parse := Value;
            elsif Name = "lexer" then
               Trace_Lexer := Value;
            elsif Name = "mckenzie" then
               Trace_McKenzie := Value;
            elsif Name = "memory" then
               Trace_Memory := Value;
            elsif Name = "parse" then
               Trace_Parse := Value;
            elsif Name = "tests" or Name = "test" then
               Trace_Tests := Value;
            elsif Name = "time" then
               Trace_Time := Value > 0;
            else
               raise User_Error with "expecting trace name, found '" & Config (Name_First .. Name_Last) & "'";
            end if;
         end;

         Name_First := Value_Last + 1;
         exit when Name_First > Config'Last;
      end loop;
   end Enable_Trace;

   procedure Enable_Trace_Help
   is
      use Ada.Text_IO;
   begin
      Put_Line (Current_Error, "debug=0|1 - show stack trace on exception, other debug settings");
      Put_Line (Current_Error, "action=n - verbosity during parse actions");
      Put_Line (Current_Error, "ebnf=n generate_ebnf=n - verbosity during translate EBNF to BNF");
      Put_Line (Current_Error, "generate=n - top level verbosity during grammar generation");
      Put_Line (Current_Error, "minimal_complete=n generate_minimal_complete=n - verbosity during minimal_complete");
      Put_Line (Current_Error, "table=n generate_table=n - verbosity during generate parse table");
      Put_Line (Current_Error, "incremental=n incremental_parse=n - verbosity during edit_tree");
      Put_Line (Current_Error, "lexer=n - verbosity during lexing");
      Put_Line (Current_Error, "mckenzie=n - verbosity during error recover");
      Put_Line (Current_Error, "parse=n - verbosity during parsing");
      Put_Line (Current_Error, "test=n - verbosity during unit tests");
      Put_Line (Current_Error, "time=n - output times of various operations");
   end Enable_Trace_Help;

   procedure Put
     (Lists : in Precedence_Lists_Arrays.Vector;
      Map   : in Precedence_Maps.Map)
   is
      use Ada.Text_IO;
      use Precedence_Maps;
   begin
      Put_Line ("Precedence_IDs:");
      for Cur in Map.Iterate loop
         Put_Line (Element (Cur)'Image & ": " & Key (Cur));
      end loop;

      Put_Line ("Precedence relations:");
      for List of Lists loop
         for P of List loop
            Put (P'Image & " ");
         end loop;
         New_Line;
      end loop;
   end Put;

   function Compare
     (Left        : in Precedence_ID;
      Right       : in Precedence_ID;
      Precedences : in Precedence_Lists_Arrays.Vector)
     return Precedence_Compare_Result
   is
   begin
      if Left = Right then
         return None;
      end if;

      for List of Precedences loop
         declare
            I           : Integer := 0;
            Left_Index  : Integer := 0;
            Right_Index : Integer := 0;
         begin
            for Prec of List loop
               I := @ + 1;
               if Left = Prec then
                  Left_Index := I;
               end if;
               if Right = Prec then
                  Right_Index := I;
               end if;
            end loop;

            if Left_Index /= 0 and Right_Index /= 0 then
               if Left_Index < Right_Index then
                  return WisiToken.Left;
               elsif Left_Index > Right_Index then
                  return WisiToken.Right;
               else
                  return WisiToken.None;
               end if;
            end if;
         end;
      end loop;
      return None;
   end Compare;

   function Error_Message
     (File_Name : in String;
      Line      : in Base_Line_Number_Type;
      Column    : in Ada.Text_IO.Count;
      Message   : in String)
     return String
   is
      use all type Ada.Text_IO.Count;
   begin
      return File_Name & ":" &
        Trimmed_Image (if Line = Invalid_Line_Number then Integer'(0) else Integer (Line)) & ":" &
        Trimmed_Image (Integer (Column + 1)) & ": " &
        Message;
   end Error_Message;

   function Next_Value
     (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
      Delims : in              Ada.Strings.Maps.Character_Set)
     return String
   is
      use Ada.Strings.Unbounded;
      Result : Unbounded_String;
      Char   : Character;
   begin
      loop
         Character'Read (Stream, Char);
         exit when not Ada.Strings.Maps.Is_In (Char, Delims);
      end loop;
      Append (Result, Char);

      loop
         Character'Read (Stream, Char);
         if Ada.Strings.Maps.Is_In (Char, Delims) then
            return To_String (Result);
         else
            Append (Result, Char);
         end if;
      end loop;
   exception
   when Ada.Streams.Stream_IO.End_Error =>
      return To_String (Result);
   end Next_Value;

end WisiToken;
