-------------------------------------------------------------------------------
--
--  This file is part of AdaBrowse.
--
-- <STRONG>Copyright (c) 2002 by Thomas Wolf.</STRONG>
-- <BLOCKQUOTE>
--    AdaBrowse 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 2, or (at your option) any
--    later version. AdaBrowse is distributed in the hope that it will be
--    useful, but <EM>without any warranty</EM>; without even the implied
--    warranty of <EM>merchantability or fitness for a particular purpose.</EM>
--    See the GNU General Public License for  more details. You should have
--    received a copy of the GNU General Public License with this distribution,
--    see file "<A HREF="GPL.txt">GPL.txt</A>". If not, write to the Free
--    Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
--    USA.
-- </BLOCKQUOTE>
--
-- <DL><DT><STRONG>
-- Author:</STRONG><DD>
--   Thomas Wolf  (TW)
--   <ADDRESS><A HREF="mailto:twolf@acm.org">twolf@acm.org</A></ADDRESS></DL>
--
-- <DL><DT><STRONG>
-- Purpose:</STRONG><DD>
--   Singled out crossreference setup from AD.Writers.</DL>
--
-- <!--
-- Revision History
--
--   20-AUG-2002   TW  Initial version.
--   27-AUG-2002   TW  Improved 'Crossref_Exp' to cater for implicitly
--                     inherited subprograms and enumeration literals for
--                     which AdaBrowse V2.13 and earlier generated empty
--                     crossrefs (<A HREF="">Name</A>). We now try to generate
--                     a crossref to the explicit declaration the item was
--                     inherited from.
--   06-JUN-2003   TW  Moved 'Set_Standard_Units' and 'Crossref_To_Unit' here
--                     from AD.Queries.
--   09-JUL-2003   TW  New operation 'Crossref_Special' for pragmas and rep
--                     clauses.
-- -->
-------------------------------------------------------------------------------

pragma License (GPL);

with Ada.Characters.Handling;
with Ada.Exceptions;
with Ada.Strings.Wide_Unbounded;

with Asis;
with Asis.Clauses;
with Asis.Compilation_Units;
with Asis.Declarations;
with Asis.Elements;
with Asis.Exceptions;
with Asis.Text;

with AD.Exclusions;
with AD.Queries;

with Asis2.Declarations;
with Asis2.Naming;
with Asis2.Spans;
with Asis2.Text;
with Asis2.Units;

package body AD.Crossrefs is

   package ACH  renames Ada.Characters.Handling;
   package WASU renames Ada.Strings.Wide_Unbounded;

   use Asis;
   use Asis.Declarations;
   use Asis.Elements;

   Do_Standard_Too : Boolean := False;

   procedure Set_Standard_Units
     (Do_Them : in Boolean)
   is
   begin
      Do_Standard_Too := Do_Them;
   end Set_Standard_Units;

   function Crossref_To_Unit
     (Unit : in Asis.Compilation_Unit)
     return Boolean
   is
   begin
      if Asis.Compilation_Units.Unit_Origin (Unit) /= An_Application_Unit then
         --  Non-application units.
         if not Do_Standard_Too or else
            Asis2.Units.Is_Standard (Unit)
         then
            return False;
         end if;
      end if;
      return
        not AD.Exclusions.No_XRef
              (ACH.To_String
                 (Asis2.Text.To_Lower
                    (Asis2.Naming.Full_Unit_Name (Unit))));
   end Crossref_To_Unit;

   function Crossref_Name
     (Name      : in     Asis.Defining_Name;
      This_Unit : in     Asis.Declaration;
      Reporter  : access AD.Messages.Error_Reporter'Class)
     return Cross_Reference
   is
      Def       : constant Defining_Name    :=
        AD.Queries.Expand_Generic (Name, Reporter);
      Decl      : constant Declaration      :=
        Asis2.Declarations.Enclosing_Declaration (Def);
      Unit      : constant Compilation_Unit :=
        Enclosing_Compilation_Unit (Def);
      Unit_Decl : constant Declaration      :=
        Unit_Declaration (Unit);
      Result    : Cross_Reference;
   begin
      Result.Ignore         := not Crossref_To_Unit (Unit);
      Result.Is_Top_Unit    := Is_Equal (Decl, Unit_Decl);
      Result.Is_Local       := Is_Equal (Unit_Decl, This_Unit);
      Result.Full_Unit_Name :=
        WASU.To_Unbounded_Wide_String (Asis2.Naming.Full_Unit_Name (Unit));
      Result.Position       := Asis2.Spans.Start (Asis2.Spans.Get_Span (Def));
      Result.Image          :=
        WASU.To_Unbounded_Wide_String
          (Asis2.Naming.Name_Definition_Image (Def));
      --  We may use the image of 'Def' even if 'Name' is the current unit's
      --  name, for in this case, Def = Name.
      return Result;
   end Crossref_Name;

   function Crossref_Special
     (Element   : in     Asis.Element;
      This_Unit : in     Asis.Declaration)
     return Cross_Reference
   is
      Unit      : constant Compilation_Unit :=
        Enclosing_Compilation_Unit (Element);
      Unit_Decl : constant Declaration      :=
        Unit_Declaration (Unit);
      Result    : Cross_Reference;
   begin
      Result.Ignore         := not Crossref_To_Unit (Unit);
      Result.Is_Top_Unit    := False;
      Result.Is_Local       := Is_Equal (Unit_Decl, This_Unit);
      Result.Full_Unit_Name :=
        WASU.To_Unbounded_Wide_String (Asis2.Naming.Full_Unit_Name (Unit));
      Result.Position       :=
        Asis2.Spans.Start (Asis2.Spans.Get_Span (Element));
      case Element_Kind (Element) is
         when A_Pragma =>
            Result.Image :=
              WASU.To_Unbounded_Wide_String (Pragma_Name_Image (Element));
         when A_Clause =>
            case Representation_Clause_Kind (Element) is
               when An_Attribute_Definition_Clause |
                    An_Enumeration_Representation_Clause |
                    A_Record_Representation_Clause =>
                  Result.Image :=
                    WASU.To_Unbounded_Wide_String
                      (Asis2.Naming.Name_Expression_Image
                        (Asis.Clauses.Representation_Clause_Name (Element)));
               when others =>
                  Ada.Exceptions.Raise_Exception
                    (Asis.Exceptions.ASIS_Inappropriate_Element'Identity,
                     "Unexpected rep clause kind " &
                     Representation_Clause_Kinds'Image
                       (Representation_Clause_Kind (Element)) &
                     " in AD.Crossrefs.Crossref_Special.");
            end case;
         when others =>
            Ada.Exceptions.Raise_Exception
              (Asis.Exceptions.ASIS_Inappropriate_Element'Identity,
               "Unexpected element kind " &
               Element_Kinds'Image (Element_Kind (Element)) &
               " in AD.Crossrefs.Crossref_Special.");
      end case;
      return Result;
   end Crossref_Special;

   function Crossref_Exp
     (Name      : in     Asis.Expression;
      This_Unit : in     Asis.Declaration;
      Reporter  : access AD.Messages.Error_Reporter'Class)
     return Cross_Reference
   is
      Def       : Defining_Name;
      Decl      : Declaration;
      True_Name : Defining_Name;
      Unit      : Asis.Compilation_Unit;
      Unit_Decl : Asis.Declaration;
      Kind      : constant Expression_Kinds := Expression_Kind (Name);
      Use_Def   : Boolean :=
        Kind = An_Identifier or else
        Kind = An_Enumeration_Literal;
   begin
      Def   := Asis2.Declarations.Name_Definition (Name);
      if Is_Nil (Def) then
         --  We may also legally get a Nil_Element here; e.g. if 'Name'
         --  refers to a dispatching call.
         return Null_Crossref;
      end if;
      Decl      := Asis2.Declarations.Enclosing_Declaration (Def);
      True_Name := AD.Queries.Expand_Generic (Def, Reporter);
      declare
         True_Decl : Asis.Declaration :=
           Asis2.Declarations.Enclosing_Declaration (True_Name);
      begin
         case Declaration_Origin (True_Decl) is
            when An_Explicit_Declaration =>
               null;
            when An_Implicit_Predefined_Declaration =>
               Use_Def := False;
               --  We couldn't generate any meaningful crossref anyway.
            when An_Implicit_Inherited_Declaration =>
               --  Oh well... let's try to find the explicit declaration from
               --  which we inherited the item. Note: it must be a function or
               --  procedure declaration, or an enumeration literal
               --  specification.
               case Declaration_Kind (True_Decl) is
                  when A_Procedure_Declaration |
                       A_Function_Declaration |
                       An_Enumeration_Literal_Specification =>
                     True_Decl :=
                       Asis2.Declarations.Real_Declaration (True_Decl);
                     if not Is_Nil (True_Decl) and then
                        not Asis.Text.Is_Nil (Asis2.Spans.Get_Span (True_Decl))
                     then
                        Decl      := True_Decl;
                        True_Name := Asis2.Naming.Get_Name (Decl);
                        --  We don't need to worry about defining expanded
                        --  names here...
                     end if;
                  when others =>
                     AD.Messages.Report_Error
                       (Reporter.all,
                        "Missing case value in AD.Crossrefs.Crossref_Exp for "
                        &
                        Asis.Declaration_Kinds'Image
                          (Declaration_Kind (True_Decl)));
               end case;
            when others =>
               null;
         end case;
      end;

      --  Special handling for references that go to items inside a generic
      --  formal package: if we have "with package X is new Y (<>)", and a
      --  reference such as "X.Z", we want to generate for 'Z' a reference
      --  to 'Y'!
      --
      --  ASIS-for-GNAT 3.14p returns an element that obviously has a source
      --  position that corresponds to the one in 'Y', but the enclosing
      --  unit delaration is not 'Y', but the one containing the formal
      --  package declaration of 'X'. Also, neither 'X', nor 'Y', nor 'Z'
      --  have Is_Part_Of_Instance.
      --
      --  I do not know whether or not this is a bug in ASIS-for-GNAT.
      --  Anyway, we handle this case specially here: if we find that a
      --  reference is inside a formal package, we use the corresponding
      --  generic unit to generate the cross-reference, *not* the unit
      --  containing the formal package declaration.
      declare
         Outer : Asis.Element := Enclosing_Element (Decl);
      begin
         while not Is_Nil (Outer) loop
            if Element_Kind (Outer) = A_Declaration then
               case Declaration_Kind (Outer) is
                  when A_Formal_Package_Declaration |
                       A_Formal_Package_Declaration_With_Box =>
                     exit;
                  when others =>
                     null;
               end case;
            end if;
            Outer := Enclosing_Element (Outer);
         end loop;
         if Is_Nil (Outer) then
            Unit      := Enclosing_Compilation_Unit (True_Name);
            Unit_Decl := Unit_Declaration (Unit);
         else
            Unit_Decl :=
              Asis2.Declarations.Name_Declaration (Generic_Unit_Name (Outer));
            Unit      := Enclosing_Compilation_Unit (Unit_Decl);
         end if;
      exception
         when others =>
            --  Revert to the default behavior:
            Unit      := Enclosing_Compilation_Unit (True_Name);
            Unit_Decl := Unit_Declaration (Unit);
      end;
      if Use_Def then
         --  Asis sometimes returns the wrong name. Shouldn't actually happen
         --  anymore with the correction in Asis2.Naming.
         Asis2.Naming.Verify_Name_Definition (Def, Name);
         Use_Def := not Is_Nil (Def);
         if not Use_Def then
            AD.Messages.Report_Error
              (Reporter.all, "ASIS returns the wrong name here!");
         end if;
      end if;
      declare
         Result : Cross_Reference;
      begin
         Result.Ignore         :=
           Is_Equal (Decl, This_Unit) or else
           not Crossref_To_Unit (Unit);
         Result.Is_Top_Unit    := Is_Equal (Decl, Unit_Decl);
         Result.Is_Local       := Is_Equal (Unit_Decl, This_Unit);
         Result.Full_Unit_Name :=
           WASU.To_Unbounded_Wide_String (Asis2.Naming.Full_Unit_Name (Unit));
         Result.Position    :=
           Asis2.Spans.Start (Asis2.Spans.Get_Span (True_Name));
         if Use_Def then
            Result.Image :=
              WASU.To_Unbounded_Wide_String
                (Asis2.Naming.Name_Definition_Image (Def));
            --  We may use the image of 'Def' even if 'Name' is the current
            --  unit's name, for in this case, Def = Name.
         else
            Result.Image := WASU.Null_Unbounded_Wide_String;
         end if;
         return Result;
      end;
   end Crossref_Exp;

end AD.Crossrefs;
