--
--  Copyright (C) 2021-2024, AdaCore
--
--  SPDX-License-Identifier: Apache-2.0 WITH LLVM-Exception
--

with Ada.Strings.Hash;
with GPR2.Project;

package body GPR2.View_Ids is

   use type GPR2.Context.Context_Kind;

   ROOT_VIEWS_PREFIX    : constant Character  := '<';
   AGGR_VIEWS_PREFIX    : constant Character  := '$';
   SPECIAL_VIEWS_PREFIX : constant Character  := '!';
   EXTENDED_PREFIX      : constant Character  := '>';

   UNDEFINED_IMAGE      : constant Value_Type := "";
   RUNTIME_IMAGE        : constant Value_Type := "runtime";
   CONFIG_IMAGE         : constant Value_Type := "config";

   -------
   -- < --
   -------

   function "<" (Self : View_Id; Other : View_Id) return Boolean is
   begin
      return Image (Self) < Image (Other);
   end "<";

   -------
   -- = --
   -------

   overriding function "=" (Self : View_Id; Other : View_Id) return Boolean is
   begin
      if Self.Kind /= Other.Kind then
         return False;
      elsif Self.Kind = Project_Id then
         return Self.Id = Other.Id
           and then Self.Context = Other.Context
           and then Self.Extending = Other.Extending;
      else
         return True;
      end if;
   end "=";

   ------------
   -- Create --
   ------------

   function Create
     (Project_File : GPR2.Path_Name.Object;
      Context      : GPR2.Context.Context_Kind := Root;
      Extending    : View_Id := Undefined)
      return View_Id
   is
   begin
      if not Project_File.Is_Defined then
         raise View_Id_Error with "cannot create view id from empty path";
      end if;

      if not Project_File.Has_Dir_Name then
         raise View_Id_Error with "cannot create view id from relative path";
      end if;

      declare
         Normalized : constant GPR2.Path_Name.Object :=
                        GPR2.Project.Create
                          (Filename_Type (Project_File.Value),
                           Resolve_Links => True);
         Id_Str     : Unbounded_String;
      begin
         Append (Id_Str, GPR2.Path_Name.To_OS_Case (Normalized.Value));

         return (Kind      => Project_Id,
                 Id        => Id_Str,
                 Context   => Context,
                 Extending => (if Is_Defined (Extending)
                               then To_Unbounded_String (String
                                   (Image (Extending)))
                               else Null_Unbounded_String));
      end;
   end Create;

   ----------
   -- Hash --
   ----------

   function Hash (Self : View_Id) return Ada.Containers.Hash_Type is
   begin
      return Ada.Strings.Hash (String (Image (Self)));
   end Hash;

   -----------
   -- Image --
   -----------

   function Image (Self : View_Id) return Value_Type is
   begin
      case Self.Kind is
         when Null_Id    => return UNDEFINED_IMAGE;
         when Config_Id  => return SPECIAL_VIEWS_PREFIX & CONFIG_IMAGE;
         when Runtime_Id => return SPECIAL_VIEWS_PREFIX & RUNTIME_IMAGE;
         when Project_Id =>
            declare
               Extending_Suffix : constant Value_Type :=
                                    (if Length (Self.Extending) = 0
                                     then ""
                                     else EXTENDED_PREFIX &
                                      Value_Type (To_String (Self.Extending)));
            begin
               if Self.Context = Root then
                  return ROOT_VIEWS_PREFIX &
                    Value_Type (To_String (Self.Id)) &
                    Extending_Suffix;
               else
                  return AGGR_VIEWS_PREFIX &
                    Value_Type (To_String (Self.Id)) &
                    Extending_Suffix;
               end if;
            end;
      end case;
   end Image;

   ------------
   -- Import --
   ------------

   function Import (Name : Value_Type) return View_Id
   is
      Prefix        : Character;
      Id            : Value_Type renames
                        Name (Name'First + 1 .. Name'Last);
      Ext_Delimiter : Natural;
      Context       : GPR2.Context.Context_Kind;

   begin
      if Name = UNDEFINED_IMAGE then
         return (Kind => Null_Id);
      end if;

      Prefix := Name (Name'First);

      if Prefix = SPECIAL_VIEWS_PREFIX then
         if Id = CONFIG_IMAGE then
            return (Kind => Config_Id);
         elsif Id = RUNTIME_IMAGE then
            return (Kind => Runtime_Id);
         else
            raise View_Id_Error with "Invalid view id image";
         end if;
      end if;

      if Prefix = ROOT_VIEWS_PREFIX then
         Context := Root;

      elsif Prefix = AGGR_VIEWS_PREFIX then
         Context := Aggregate;

      else
         raise View_Id_Error with "invalid view id image";
      end if;

      Ext_Delimiter := 0;

      for J in Id'Range loop
         if Id (J) = EXTENDED_PREFIX then
            Ext_Delimiter := J;
            exit;
         end if;
      end loop;

      if Ext_Delimiter = 0 then
         return (Kind      => Project_Id,
                 Id        => To_Unbounded_String (String (Id)),
                 Context   => Context,
                 Extending => Null_Unbounded_String);
      else
         return (Kind      => Project_Id,
                 Id        => To_Unbounded_String (String
                                (Id (Id'First .. Ext_Delimiter - 1))),
                 Context   => Context,
                 Extending => To_Unbounded_String (String
                                (Id (Ext_Delimiter + 1 .. Id'Last))));
      end if;
   end Import;

   --------------------
   -- Is_Valid_Image --
   --------------------

   function Is_Valid_Image (Name : Value_Type) return Boolean is
   begin
      if Name'Length = 0 then
         return True;

      elsif Name (Name'First) = SPECIAL_VIEWS_PREFIX then
         return Name = SPECIAL_VIEWS_PREFIX & CONFIG_IMAGE
           or else Name = SPECIAL_VIEWS_PREFIX & RUNTIME_IMAGE;

      else
         return Name (Name'First) = ROOT_VIEWS_PREFIX
           or else Name (Name'First) = AGGR_VIEWS_PREFIX;
      end if;
   end Is_Valid_Image;

end GPR2.View_Ids;
