------------------------------------------------------------------------------
--                         Language Server Protocol                         --
--                                                                          --
--                     Copyright (C) 2018-2024, 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 Ada.Directories;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Fixed;
with Ada.Strings.Maps;

with GNAT.Regpat;
with GNAT.OS_Lib;

with VSS.Strings.Conversions;

with URIs;

package body Tester.Macros is

   function Expand
     (Value    : GNATCOLL.JSON.JSON_Value;
      Env      : Spawn.Environments.Process_Environment;
      Test_Dir : String) return GNATCOLL.JSON.JSON_Value;
   --  Expand recursively

   function Expand_FILE
     (Path     : String;
      Test_Dir : String) return String;
   --  Turn Path into file absolute path

   function Expand_URI
     (Path     : String;
      Test_Dir : String) return String;
   --  Turn Path into URI with scheme 'file://'

   Pattern : constant GNAT.Regpat.Pattern_Matcher :=
     GNAT.Regpat.Compile ("\${([\w]+)}|\$URI{([^}]*)}|\$FILE{([^}]*)}");

   Replace_Slash : constant Ada.Strings.Maps.Character_Mapping :=
     Ada.Strings.Maps.To_Mapping
       (From => "/",
        To   => GNAT.OS_Lib.Directory_Separator & "");

   function Expand
     (Value    : GNATCOLL.JSON.JSON_Value;
      Env      : Spawn.Environments.Process_Environment;
      Test_Dir : String) return GNATCOLL.JSON.JSON_Value
   is
      procedure Each_Field
        (Object : in out GNATCOLL.JSON.JSON_Value;
         Name   : String;
         Value  : GNATCOLL.JSON.JSON_Value);
      --  Expand macros in given field

      function Expand_In_String (Text : String) return Unbounded_String;
      --  Expand macro in given string

      procedure Map is new GNATCOLL.JSON.Gen_Map_JSON_Object
        (GNATCOLL.JSON.JSON_Value);

      ----------------
      -- Each_Field --
      ----------------

      procedure Each_Field
        (Object : in out GNATCOLL.JSON.JSON_Value;
         Name   : String;
         Value  : GNATCOLL.JSON.JSON_Value)
      is
         Key : constant String := To_String (Expand_In_String (Name));
      begin
         Object.Set_Field (Key, Expand (Value, Env, Test_Dir));
      end Each_Field;

      ----------------------
      -- Expand_In_String --
      ----------------------

      function Expand_In_String (Text : String) return Unbounded_String
      is
         use type GNAT.Regpat.Match_Location;
         Result : Unbounded_String;
         Next   : Positive := 1;
      begin
         while Next < Text'Length loop
            declare
               Found : GNAT.Regpat.Match_Array (0 .. 3);
            begin
               GNAT.Regpat.Match (Pattern, Text, Found, Next);
               exit when Found (0) = GNAT.Regpat.No_Match;

               Append (Result, Text (Next .. Found (0).First - 1));

               if Found (1) /= GNAT.Regpat.No_Match then     --  ${NAME}
                  declare
                     Name : constant String :=
                       Text (Found (1).First .. Found (1).Last);
                  begin
                     if Env.Contains (Name) then
                        Append (Result, Env.Value (Name));
                     else
                        raise Program_Error
                          with Name & " undefined, cannot expand macro";
                     end if;
                  end;
               elsif Found (2) /= GNAT.Regpat.No_Match then  --  $URI{x}
                  Append
                    (Result,
                     Expand_URI
                       (Text (Found (2).First .. Found (2).Last),
                        Test_Dir));
               elsif Found (3) /= GNAT.Regpat.No_Match then  --  $FILE{x}
                  Append
                    (Result,
                     Expand_FILE
                       (Text (Found (3).First .. Found (3).Last),
                        Test_Dir));
               end if;

               Next := Found (0).Last + 1;
            end;
         end loop;

         Append (Result, Text (Next .. Text'Last));

         return Result;
      end Expand_In_String;

   begin
      case Value.Kind is
         when GNATCOLL.JSON.JSON_Null_Type |
              GNATCOLL.JSON.JSON_Boolean_Type |
              GNATCOLL.JSON.JSON_Int_Type |
              GNATCOLL.JSON.JSON_Float_Type =>

            return Value;
         when GNATCOLL.JSON.JSON_String_Type =>

            return GNATCOLL.JSON.Create (Expand_In_String (Value.Get));
         when GNATCOLL.JSON.JSON_Array_Type =>
            declare
               Result : GNATCOLL.JSON.JSON_Array;
               Vector : constant GNATCOLL.JSON.JSON_Array := Value.Get;
            begin
               for J in 1 .. GNATCOLL.JSON.Length (Vector) loop
                  declare
                     Item : constant GNATCOLL.JSON.JSON_Value :=
                       GNATCOLL.JSON.Get (Vector, J);
                  begin
                     GNATCOLL.JSON.Append
                       (Result, Expand (Item, Env, Test_Dir));
                  end;
               end loop;

               return GNATCOLL.JSON.Create (Result);
            end;
         when GNATCOLL.JSON.JSON_Object_Type =>
            declare
               Result : GNATCOLL.JSON.JSON_Value :=
                 GNATCOLL.JSON.Create_Object;
            begin
               Map (Value, Each_Field'Access, Result);

               return Result;
            end;
      end case;
   end Expand;

   ------------
   -- Expand --
   ------------

   procedure Expand
     (Test : in out GNATCOLL.JSON.JSON_Value;
      Env  : Spawn.Environments.Process_Environment;
      Path : VSS.Strings.Virtual_String)
   is
      File      : constant String :=
        VSS.Strings.Conversions.To_UTF_8_String (Path);

      Full_Name : constant String := Ada.Directories.Full_Name (File);

      Directory : constant String :=
        Ada.Directories.Containing_Directory (Full_Name);

      Env_With_Dir : Spawn.Environments.Process_Environment := Env;
   begin
      Env_With_Dir.Insert ("DIR", Directory);
      Env_With_Dir.Insert ("DIR_SEP", GNAT.OS_Lib.Directory_Separator & "");
      Test := Expand (Test, Env_With_Dir, Directory);
   end Expand;

   -----------------
   -- Expand_FILE --
   -----------------

   function Expand_FILE
     (Path     : String;
      Test_Dir : String) return String
   is
      Argument : constant String := Ada.Strings.Fixed.Translate
        (Path, Replace_Slash);
   begin
      if Path = "" then
         --  Return normalized Test_Dir
         return Ada.Directories.Full_Name (Test_Dir);

      elsif Path = "/" then
         --  Return normalized Test_Dir & '/'
         return Ada.Directories.Full_Name (Test_Dir) &
           GNAT.OS_Lib.Directory_Separator;

      else
         --  Turn a relative path into absolute path
         return Ada.Directories.Full_Name (Test_Dir) &
           GNAT.OS_Lib.Directory_Separator &
           Argument;
      end if;
   end Expand_FILE;

   ----------------
   -- Expand_URI --
   ----------------

   function Expand_URI
     (Path     : String;
      Test_Dir : String) return String
   is
   begin
      return URIs.Conversions.From_File (Expand_FILE (Path, Test_Dir));
   end Expand_URI;

end Tester.Macros;
