(**************************************************************************)
(*                                                                        *)
(*                                 OCaml                                  *)
(*                                                                        *)
(*             Maxence Guesdon, projet Cristal, INRIA Rocquencourt        *)
(*                                                                        *)
(*   Copyright 2001 Institut National de Recherche en Informatique et     *)
(*     en Automatique.                                                    *)
(*                                                                        *)
(*   All rights reserved.  This file is distributed under the terms of    *)
(*   the GNU Lesser General Public License version 2.1, with the          *)
(*   special exception on linking described in the file LICENSE.          *)
(*                                                                        *)
(**************************************************************************)

(** Cross referencing. *)

open Odoc_module
open Odoc_class
open Odoc_extension
open Odoc_exception
open Odoc_types
open Odoc_value
open Odoc_type
open Odoc_parameter

(*** Replacements of aliases : if e1 = e2 and e2 = e3, then replace e2 by e3 to have e1 = e3,
   in order to associate the element with complete information. *)

(** The module used to keep what refs were modified. *)
module S = Set.Make
    (
     struct type t = string * ref_kind option
       let compare = Stdlib.compare
     end
    )

let verified_refs = ref S.empty

let add_verified v = verified_refs := S.add v !verified_refs
let was_verified v = S.mem v !verified_refs

(** The module with the predicates used to get the aliased modules, classes and exceptions. *)
module P_alias =
  struct
    type t = int

    let p_module m _ =
      (true,
       match m.m_kind with
         Module_alias _ -> true
       | _ -> false
      )
    let p_module_type mt _ =
      (true,
       match mt.mt_kind with
         Some (Module_type_alias _) -> true
       | _ -> false
      )
    let p_class _ _ = (false, false)
    let p_class_type _ _ = (false, false)
    let p_value _ _ = false
    let p_recfield _ _ _ = false
    let p_const _ _ _ = false
    let p_type _ _ = (false, false)
    let p_extension x _ = x.xt_alias <> None
    let p_exception e _ = e.ex_alias <> None
    let p_attribute _ _ = false
    let p_method _ _ = false
    let p_section _ _ = false
  end

(** The module used to get the aliased elements. *)
module Search_alias = Odoc_search.Search (P_alias)

type alias_state =
  | Alias_to_resolve

(** Couples of module name aliases. *)
let (module_aliases : (Name.t, Name.t * alias_state) Hashtbl.t) = Hashtbl.create 13

(** Couples of module or module type name aliases. *)
let module_and_modtype_aliases = Hashtbl.create 13

(** Couples of extension name aliases. *)
let extension_aliases = Hashtbl.create 13

(** Couples of exception name aliases. *)
let exception_aliases = Hashtbl.create 13

let rec build_alias_list = function
    [] -> ()
  | (Odoc_search.Res_module m) :: q ->
      (
       match m.m_kind with
         Module_alias ma ->
           Hashtbl.add module_aliases m.m_name (ma.ma_name, Alias_to_resolve);
           Hashtbl.add module_and_modtype_aliases m.m_name (ma.ma_name, Alias_to_resolve)
       | _ -> ()
      );
      build_alias_list q
  | (Odoc_search.Res_module_type mt) :: q ->
      (
       match mt.mt_kind with
         Some (Module_type_alias mta) ->
           Hashtbl.add module_and_modtype_aliases
             mt.mt_name (mta.mta_name, Alias_to_resolve)
       | _ -> ()
      );
      build_alias_list q
  | (Odoc_search.Res_extension x) :: q ->
      (
       match x.xt_alias with
         None -> ()
       | Some xa ->
           Hashtbl.add extension_aliases
             x.xt_name (xa.xa_name,Alias_to_resolve)
      );
      build_alias_list q
  | (Odoc_search.Res_exception e) :: q ->
      (
       match e.ex_alias with
         None -> ()
       | Some ea ->
           Hashtbl.add exception_aliases
             e.ex_name (ea.ea_name,Alias_to_resolve)
      );
      build_alias_list q
  | _ :: q ->
      build_alias_list q

(** Retrieve the aliases for modules, module types and exceptions
   and put them in global hash tables. *)
let get_alias_names module_list =
  Hashtbl.clear module_aliases;
  Hashtbl.clear module_and_modtype_aliases;
  Hashtbl.clear extension_aliases;
  Hashtbl.clear exception_aliases;
  build_alias_list (Search_alias.search module_list 0)

module Ele_map = Misc.Stdlib.String.Map

let known_elements = ref Ele_map.empty
let add_known_element name k =
  try
    let l = Ele_map.find name !known_elements in
    let s = Ele_map.remove name !known_elements in
    known_elements := Ele_map.add name (k::l) s
  with
    Not_found ->
      known_elements := Ele_map.add name [k] !known_elements

let get_known_elements name =
  try Ele_map.find name !known_elements
  with Not_found -> []

let kind_name_exists kind =
  let pred =
    match kind with
      RK_module -> (fun e -> match e with Odoc_search.Res_module _ -> true | _ -> false)
    | RK_module_type -> (fun e -> match e with Odoc_search.Res_module_type _ -> true | _ -> false)
    | RK_class -> (fun e -> match e with Odoc_search.Res_class _ -> true | _ -> false)
    | RK_class_type -> (fun e -> match e with Odoc_search.Res_class_type _ -> true | _ -> false)
    | RK_value -> (fun e -> match e with Odoc_search.Res_value _ -> true | _ -> false)
    | RK_type -> (fun e -> match e with Odoc_search.Res_type _ -> true | _ -> false)
    | RK_extension -> (fun e -> match e with Odoc_search.Res_extension _ -> true | _ -> false)
    | RK_exception -> (fun e -> match e with Odoc_search.Res_exception _ -> true | _ -> false)
    | RK_attribute -> (fun e -> match e with Odoc_search.Res_attribute _ -> true | _ -> false)
    | RK_method -> (fun e -> match e with Odoc_search.Res_method _ -> true | _ -> false)
    | RK_section _ -> assert false
    | RK_recfield -> (fun e -> match e with Odoc_search.Res_recfield _ -> true | _ -> false)
    | RK_const -> (fun e -> match e with Odoc_search.Res_const _ -> true | _ -> false)
  in
  fun name ->
    try List.exists pred (get_known_elements name)
    with Not_found -> false

let module_exists = kind_name_exists RK_module
let module_type_exists = kind_name_exists RK_module_type
let class_exists = kind_name_exists RK_class
let class_type_exists = kind_name_exists RK_class_type
let value_exists = kind_name_exists RK_value
let type_exists = kind_name_exists RK_type
let extension_exists = kind_name_exists RK_extension
let exception_exists = kind_name_exists RK_exception
let attribute_exists = kind_name_exists RK_attribute
let method_exists = kind_name_exists RK_method
let recfield_exists = kind_name_exists RK_recfield
let const_exists = kind_name_exists RK_const

let lookup_module name =
  match List.find
      (fun k -> match k with Odoc_search.Res_module _ -> true | _ -> false)
      (get_known_elements name)
  with
  | Odoc_search.Res_module m -> m
  | _ -> assert false

let lookup_module_type name =
  match List.find
      (fun k -> match k with Odoc_search.Res_module_type _ -> true | _ -> false)
      (get_known_elements name)
  with
  | Odoc_search.Res_module_type m -> m
  | _ -> assert false

let lookup_class name =
  match List.find
      (fun k -> match k with Odoc_search.Res_class _ -> true | _ -> false)
      (get_known_elements name)
  with
  | Odoc_search.Res_class c -> c
  | _ -> assert false

let lookup_class_type name =
  match List.find
      (fun k -> match k with Odoc_search.Res_class_type _ -> true | _ -> false)
      (get_known_elements name)
  with
  | Odoc_search.Res_class_type c -> c
  | _ -> assert false

let lookup_extension name =
  match List.find
      (fun k -> match k with Odoc_search.Res_extension _ -> true | _ -> false)
      (get_known_elements name)
  with
  | Odoc_search.Res_extension x -> x
  | _ -> assert false

let lookup_exception name =
  match List.find
      (fun k -> match k with Odoc_search.Res_exception _ -> true | _ -> false)
      (get_known_elements name)
  with
  | Odoc_search.Res_exception e -> e
  | _ -> assert false

class scan =
  object
    inherit Odoc_scan.scanner
    method! scan_value v =
      add_known_element v.val_name (Odoc_search.Res_value v)
    method! scan_type_recfield t f =
      add_known_element
        (Printf.sprintf "%s.%s" t.ty_name f.rf_name)
        (Odoc_search.Res_recfield (t, f))
    method! scan_type_const t f =
      add_known_element
        (Printf.sprintf "%s.%s" t.ty_name f.vc_name)
        (Odoc_search.Res_const (t, f))
    method! scan_type_pre t =
      add_known_element t.ty_name (Odoc_search.Res_type t);
      true
    method! scan_extension_constructor x =
      add_known_element x.xt_name (Odoc_search.Res_extension x)
    method! scan_exception e =
      add_known_element e.ex_name (Odoc_search.Res_exception e)
    method! scan_attribute a =
      add_known_element a.att_value.val_name
        (Odoc_search.Res_attribute a)
    method! scan_method m =
      add_known_element m.met_value.val_name
        (Odoc_search.Res_method m)
    method! scan_class_pre c =
      add_known_element c.cl_name (Odoc_search.Res_class c);
      true
    method! scan_class_type_pre c =
      add_known_element c.clt_name (Odoc_search.Res_class_type c);
      true
    method! scan_module_pre m =
      add_known_element m.m_name (Odoc_search.Res_module m);
      true
    method! scan_module_type_pre m =
      add_known_element m.mt_name (Odoc_search.Res_module_type m);
      true

  end

let init_known_elements_map module_list =
  let c = new scan in
  c#scan_module_list module_list


(** The type to describe the names not found. *)
type not_found_name =
  | NF_mt of Name.t
  | NF_mmt of Name.t
  | NF_c of Name.t
  | NF_cct of Name.t
  | NF_xt of Name.t
  | NF_ex of Name.t

(** Functions to find and associate aliases elements. *)

let rec associate_in_module module_list (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) m =
  let rec iter_kind (acc_b, acc_inc, acc_names) k =
    match k with
      Module_struct elements ->
        List.fold_left
          (associate_in_module_element module_list m.m_name)
          (acc_b, acc_inc, acc_names)
          elements

    | Module_alias ma ->
        (
         match ma.ma_module with
           Some _ ->
             (acc_b, acc_inc, acc_names)
         | None ->
             let mmt_opt =
               try Some (Mod (lookup_module ma.ma_name))
               with Not_found ->
                 try Some (Modtype (lookup_module_type ma.ma_name))
                 with Not_found -> None
             in
             match mmt_opt with
               None -> (acc_b, (Name.head m.m_name) :: acc_inc,
                        (* we don't want to output warning messages for
                           "sig ... end" or "struct ... end" modules not found *)
                        (if ma.ma_name = Odoc_messages.struct_end ||
                          ma.ma_name = Odoc_messages.sig_end then
                          acc_names
                        else
                          (NF_mmt ma.ma_name) :: acc_names)
                       )
             | Some mmt ->
                 ma.ma_module <- Some mmt ;
                 (true, acc_inc, acc_names)
        )

    | Module_functor (_, k) ->
        iter_kind (acc_b, acc_inc, acc_names) k

    | Module_with (tk, _) ->
        associate_in_module_type module_list (acc_b, acc_inc, acc_names)
          { mt_name = "" ; mt_info = None ; mt_type = None ;
            mt_is_interface = false ; mt_file = ""; mt_kind = Some tk ;
            mt_loc = Odoc_types.dummy_loc }

    | Module_apply (k1, k2) ->
        let (acc_b2, acc_inc2, acc_names2) = iter_kind (acc_b, acc_inc, acc_names) k1 in
        iter_kind (acc_b2, acc_inc2, acc_names2) k2

    | Module_apply_unit k1 ->
        iter_kind (acc_b, acc_inc, acc_names) k1

    | Module_constraint (k, tk) ->
        let (acc_b2, acc_inc2, acc_names2) = iter_kind (acc_b, acc_inc, acc_names) k in
        associate_in_module_type module_list (acc_b2, acc_inc2, acc_names2)
          { mt_name = "" ; mt_info = None ; mt_type = None ;
            mt_is_interface = false ; mt_file = "" ; mt_kind = Some tk ;
            mt_loc = Odoc_types.dummy_loc }

     | Module_typeof _ ->
        (acc_b, acc_inc, acc_names)

     | Module_unpack (_code, mta) ->
        begin
          match mta.mta_module with
            Some _ ->
              (acc_b, acc_inc, acc_names)
          | None ->
              let mt_opt =
                try Some (lookup_module_type mta.mta_name)
                with Not_found -> None
              in
              match mt_opt with
                None -> (acc_b, (Name.head m.m_name) :: acc_inc,
                   (* we don't want to output warning messages for
                      "sig ... end" or "struct ... end" modules not found *)
                   (if mta.mta_name = Odoc_messages.struct_end ||
                      mta.mta_name = Odoc_messages.sig_end then
                      acc_names
                    else
                      (NF_mt mta.mta_name) :: acc_names)
                  )
              | Some mt ->
                  mta.mta_module <- Some mt ;
                  (true, acc_inc, acc_names)
        end
  in
  iter_kind (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) m.m_kind

and associate_in_module_type module_list (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) mt =
  let rec iter_kind (acc_b, acc_inc, acc_names) k =
    match k with
      Module_type_struct elements ->
        List.fold_left
          (associate_in_module_element module_list mt.mt_name)
          (acc_b, acc_inc, acc_names)
          elements

    | Module_type_functor (_, k) ->
        iter_kind (acc_b, acc_inc, acc_names) k

    | Module_type_with (k, _) ->
        iter_kind (acc_b, acc_inc, acc_names) k

    | Module_type_alias mta ->
        begin
          match mta.mta_module with
            Some _ ->
              (acc_b, acc_inc, acc_names)
          | None ->
              let mta_name =
                Name.get_relative_opt
                  !Odoc_global.library_namespace
                  mta.mta_name in
              let mt_opt =
                try Some (lookup_module_type mta_name)
                with Not_found -> None
              in
              match mt_opt with
                None -> (acc_b, (Name.head mt.mt_name) :: acc_inc,
                   (* we don't want to output warning messages for
                      "sig ... end" or "struct ... end" modules not found *)
                   (if mta.mta_name = Odoc_messages.struct_end ||
                      mta.mta_name = Odoc_messages.sig_end then
                      acc_names
                    else
                      (NF_mt mta_name) :: acc_names)
                  )
              | Some mt ->
                  mta.mta_module <- Some mt ;
                  (true, acc_inc, acc_names)
        end
    | Module_type_typeof _ ->
        (acc_b, acc_inc, acc_names)
  in
  match mt.mt_kind with
    None -> (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found)
  | Some k -> iter_kind (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) k

and associate_in_module_element module_list m_name (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) element =
   match element with
     Element_module m -> associate_in_module module_list (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) m
   | Element_module_type mt ->
       associate_in_module_type module_list (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) mt
   | Element_included_module im ->
       (
        match im.im_module with
          Some _ -> (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found)
        | None ->
            let mmt_opt =
              try Some (Mod (lookup_module im.im_name))
              with Not_found ->
                try Some (Modtype (lookup_module_type im.im_name))
                with Not_found -> None
            in
            match mmt_opt with
              None -> (acc_b_modif, (Name.head m_name) :: acc_incomplete_top_module_names,
                       (* we don't want to output warning messages for
                           "sig ... end" or "struct ... end" modules not found *)
                        (if im.im_name = Odoc_messages.struct_end ||
                          im.im_name = Odoc_messages.sig_end then
                          acc_names_not_found
                        else
                          (NF_mmt im.im_name) :: acc_names_not_found)
                      )
            | Some mmt ->
                im.im_module <- Some mmt ;
                (true, acc_incomplete_top_module_names, acc_names_not_found)
       )
   | Element_class cl -> associate_in_class module_list (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) cl
   | Element_class_type ct ->
       associate_in_class_type module_list (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) ct
   | Element_value _ -> (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found)
   | Element_type_extension te ->
       associate_in_type_extension module_list (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) te
   | Element_exception ex ->
       (
        match ex.ex_alias with
          None -> (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found)
        | Some ea ->
            match ea.ea_ex with
              Some _ ->
                (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found)
            | None ->
                let ex_opt =
                  try Some (lookup_exception ea.ea_name)
                  with Not_found -> None
                in
                match ex_opt with
                  None -> (acc_b_modif,
                           (Name.head m_name) :: acc_incomplete_top_module_names,
                           (NF_ex ea.ea_name) :: acc_names_not_found)
                | Some e ->
                    ea.ea_ex <- Some e ;
                    (true, acc_incomplete_top_module_names, acc_names_not_found)
       )
   | Element_type _ -> (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found)
   | Element_module_comment _ -> (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found)

and associate_in_class module_list (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) c =
  let rec iter_kind (acc_b, acc_inc, acc_names) k =
    match k with
      Class_structure (inher_l, _) ->
        let f (acc_b2, acc_inc2, acc_names2) ic =
          match ic.ic_class with
          Some _ -> (acc_b2, acc_inc2, acc_names2)
        | None ->
            let cct_opt =
              try Some (Cl (lookup_class ic.ic_name))
              with Not_found ->
                try Some (Cltype (lookup_class_type ic.ic_name, []))
                with Not_found -> None
            in
            match cct_opt with
              None -> (acc_b2, (Name.head c.cl_name) :: acc_inc2,
                       (* we don't want to output warning messages for "object ... end" classes not found *)
                       (if ic.ic_name = Odoc_messages.object_end then acc_names2 else (NF_cct ic.ic_name) :: acc_names2))
            | Some cct ->
                ic.ic_class <- Some cct ;
                (true, acc_inc2, acc_names2)
        in
        List.fold_left f (acc_b, acc_inc, acc_names) inher_l

    | Class_apply capp ->
        (
         match capp.capp_class with
           Some _ ->  (acc_b, acc_inc, acc_names)
         | None ->
             let cl_opt =
               try Some (lookup_class capp.capp_name)
               with Not_found -> None
             in
             match cl_opt with
               None -> (acc_b, (Name.head c.cl_name) :: acc_inc,
                        (* we don't want to output warning messages for "object ... end" classes not found *)
                        (if capp.capp_name = Odoc_messages.object_end then acc_names else (NF_c capp.capp_name) :: acc_names))
             | Some c ->
                 capp.capp_class <- Some c ;
                 (true, acc_inc, acc_names)
        )

    | Class_constr cco ->
        (
         match cco.cco_class with
           Some _ ->  (acc_b, acc_inc, acc_names)
         | None ->
             let cl_opt =
               try Some (lookup_class cco.cco_name)
               with Not_found -> None
             in
             match cl_opt with
               None ->
                 (
                  let clt_opt =
                    try Some (lookup_class_type cco.cco_name)
                    with Not_found -> None
                  in
                  match clt_opt with
                    None ->
                      (acc_b, (Name.head c.cl_name) :: acc_inc,
                        (* we don't want to output warning messages for "object ... end" classes not found *)
                       (if cco.cco_name = Odoc_messages.object_end then acc_names else (NF_cct cco.cco_name) :: acc_names))
                  | Some ct ->
                      cco.cco_class <- Some (Cltype (ct, [])) ;
                      (true, acc_inc, acc_names)
                 )
             | Some c ->
                 cco.cco_class <- Some (Cl c) ;
                 (true, acc_inc, acc_names)
        )
    | Class_constraint (ckind, ctkind) ->
        let (acc_b2, acc_inc2, acc_names2) = iter_kind (acc_b, acc_inc, acc_names) ckind in
        associate_in_class_type module_list (acc_b2, acc_inc2, acc_names2)
            { clt_name = "" ; clt_info = None ;
              clt_type = c.cl_type ; (* should be ok *)
              clt_type_parameters = [] ;
              clt_virtual = false ;
              clt_kind = ctkind ;
              clt_loc = Odoc_types.dummy_loc }
  in
  iter_kind (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) c.cl_kind

and associate_in_class_type _module_list (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) ct =
  let iter_kind (acc_b, acc_inc, acc_names) k =
    match k with
      Class_signature (inher_l, _) ->
        let f (acc_b2, acc_inc2, acc_names2) ic =
          match ic.ic_class with
            Some _ -> (acc_b2, acc_inc2, acc_names2)
          | None ->
              let cct_opt =
                try Some (Cltype (lookup_class_type ic.ic_name, []))
                with Not_found ->
                  try Some (Cl (lookup_class ic.ic_name))
                  with Not_found -> None
              in
              match cct_opt with
                None -> (acc_b2, (Name.head ct.clt_name) :: acc_inc2,
                         (* we don't want to output warning messages for "object ... end" class types not found *)
                         (if ic.ic_name = Odoc_messages.object_end then acc_names2 else (NF_cct ic.ic_name) :: acc_names2))
              | Some cct ->
                  ic.ic_class <- Some cct ;
                  (true, acc_inc2, acc_names2)
        in
        List.fold_left f (acc_b, acc_inc, acc_names) inher_l

    | Class_type cta ->
        (
         match cta.cta_class with
           Some _ ->  (acc_b, acc_inc, acc_names)
         | None ->
             let cct_opt =
               try Some (Cltype (lookup_class_type cta.cta_name, []))
               with Not_found ->
                 try Some (Cl (lookup_class cta.cta_name))
                 with Not_found -> None
             in
             match cct_opt with
               None -> (acc_b, (Name.head ct.clt_name) :: acc_inc,
                        (* we don't want to output warning messages for "object ... end" class types not found *)
                        (if cta.cta_name = Odoc_messages.object_end then acc_names else (NF_cct cta.cta_name) :: acc_names))
             | Some c ->
                 cta.cta_class <- Some c ;
                 (true, acc_inc, acc_names)
        )
  in
  iter_kind (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) ct.clt_kind

and associate_in_type_extension _module_list (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) te =
  List.fold_left
    (fun (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found) xt ->
       match xt.xt_alias with
           None -> (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found)
         | Some xa ->
             match xa.xa_xt with
                 Some _ ->
                   (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found)
               | None ->
                   let xt_opt =
                     try Some (lookup_extension xa.xa_name)
                     with Not_found -> None
                   in
                     match xt_opt with
                         None -> (acc_b_modif,
                                  (Name.head xt.xt_name) :: acc_incomplete_top_module_names,
                                  (NF_xt xa.xa_name) :: acc_names_not_found)
                       | Some x ->
                           xa.xa_xt <- Some x ;
                           (true, acc_incomplete_top_module_names, acc_names_not_found))
    (acc_b_modif, acc_incomplete_top_module_names, acc_names_not_found)
    te.te_constructors


(*************************************************************)
(** Association of types to elements referenced in comments .*)

let ao = Odoc_misc.apply_opt

let not_found_of_kind kind name =
  (match kind with
    RK_module -> Odoc_messages.cross_module_not_found
  | RK_module_type -> Odoc_messages.cross_module_type_not_found
  | RK_class -> Odoc_messages.cross_class_not_found
  | RK_class_type -> Odoc_messages.cross_class_type_not_found
  | RK_value -> Odoc_messages.cross_value_not_found
  | RK_type -> Odoc_messages.cross_type_not_found
  | RK_extension -> Odoc_messages.cross_extension_not_found
  | RK_exception -> Odoc_messages.cross_exception_not_found
  | RK_attribute -> Odoc_messages.cross_attribute_not_found
  | RK_method -> Odoc_messages.cross_method_not_found
  | RK_section _ -> Odoc_messages.cross_section_not_found
  | RK_recfield -> Odoc_messages.cross_recfield_not_found
  | RK_const -> Odoc_messages.cross_const_not_found
  ) name

let query module_list name =
   match get_known_elements name with
     | [] ->
         (
         try
           let re = Str.regexp ("^"^(Str.quote name)^"$") in
            let t = Odoc_search.find_section module_list re in
            let v2 = (name, Some (RK_section t)) in
            add_verified v2 ;
            (name, Some (RK_section t))
          with
            Not_found ->
              (name, None)
         )
     | ele :: _ ->
        (* we look for the first element with this name *)
        let (name, kind) =
          match ele with
            Odoc_search.Res_module m -> (m.m_name, RK_module)
          | Odoc_search.Res_module_type mt -> (mt.mt_name, RK_module_type)
          | Odoc_search.Res_class c -> (c.cl_name, RK_class)
          | Odoc_search.Res_class_type ct -> (ct.clt_name, RK_class_type)
          | Odoc_search.Res_value v -> (v.val_name, RK_value)
          | Odoc_search.Res_type t -> (t.ty_name, RK_type)
          | Odoc_search.Res_extension x -> (x.xt_name, RK_extension)
          | Odoc_search.Res_exception e -> (e.ex_name, RK_exception)
          | Odoc_search.Res_attribute a -> (a.att_value.val_name, RK_attribute)
          | Odoc_search.Res_method m -> (m.met_value.val_name, RK_method)
          | Odoc_search.Res_section _-> assert false
          | Odoc_search.Res_recfield (t, f) ->
              (Printf.sprintf "%s.%s" t.ty_name f.rf_name, RK_recfield)
          | Odoc_search.Res_const (t, f) ->
              (Printf.sprintf "%s.%s" t.ty_name f.vc_name, RK_const)
        in
        add_verified (name, Some kind) ;
        (name, Some kind)


let rec search_within_ancestry
    (finalize,initial_name,query as param) ?parent_name name =
  let name = Odoc_name.normalize_name name in
  let res = query name in
  match res with
  | (name, Some k) -> finalize (Some (name,k))
  | (_, None) ->
      match parent_name with
      | None ->
          finalize None
      (* *)
      | Some p ->
          let parent_name =
            match Name.father p with
              "" -> None
            | s -> Some s
          in
          search_within_ancestry param
            ?parent_name (Name.concat p initial_name)

let search_within_ancestry finalize query ?parent_name name =
  search_within_ancestry (finalize, name, query) ?parent_name name


let rec assoc_comments_text_elements parent_name module_list t_ele =
  match t_ele with
  | Raw _
  | CodePre _
  | Latex _
  | Verbatim _ -> t_ele
  | Bold t -> Bold (assoc_comments_text parent_name module_list t)
  | Italic t -> Italic (assoc_comments_text parent_name module_list t)
  | Center t -> Center (assoc_comments_text parent_name module_list t)
  | Left t -> Left (assoc_comments_text parent_name module_list t)
  | Right t -> Right (assoc_comments_text parent_name module_list t)
  | Emphasize t -> Emphasize (assoc_comments_text parent_name module_list t)
  | List l -> List (List.map (assoc_comments_text parent_name module_list) l)
  | Enum l -> Enum (List.map (assoc_comments_text parent_name module_list) l)
  | Newline -> Newline
  | Block t -> Block (assoc_comments_text parent_name module_list t)
  | Superscript t -> Superscript (assoc_comments_text parent_name module_list t)
  | Subscript t -> Subscript (assoc_comments_text parent_name module_list t)
  | Title (n, l_opt, t) -> Title (n, l_opt, (assoc_comments_text parent_name module_list t))
  | Link (s, t) -> Link (s, (assoc_comments_text parent_name module_list t))
  | Ref (initial_name, None, text_option) ->
      let finalize = function
        | Some (name,k) -> Ref (name, Some k, text_option)
        | None ->
            Odoc_global.pwarning
              (Odoc_messages.cross_element_not_found initial_name);
            Ref (initial_name, None, text_option) in
      search_within_ancestry finalize (query module_list) ~parent_name initial_name
  | Code s ->
      if not !Odoc_global.show_missed_crossref then
        t_ele
      else (* Check if s could be turned into a valid cross-reference *)
      let name = String.trim s in
      begin
        (* First, we ignore code fragments with more than one space-separated
           words: "word1 word2" *)
        try  (ignore (String.index name ' '); t_ele)
        with Not_found ->
          if name = "" then t_ele
          else
            let first_char = name.[0] in
            (* Then, we only consider code fragments which start with a
               distinctly uppercase letter *)
            if Char.uppercase_ascii first_char <> first_char ||
               Char.lowercase_ascii first_char = first_char then
              t_ele
            else
              (* Some path analysis auxiliary functions *)
              let path s =
                String.split_on_char '.' s
              in
              let filter =
                List.filter
                  (fun s -> s <> "" && s.[0] = Char.uppercase_ascii s.[0]) in
              let rec is_prefix prefix full =
                match prefix, full with
                | [], _ -> true
                | a :: pre, b :: f when a = b -> is_prefix pre f
                | _ -> false in
              let p = filter @@ path name and parent_p = path parent_name in
              let is_path_suffix () =
                is_prefix (List.rev @@ p) (List.rev @@ parent_p ) in
              (* heuristic:
                 - if name = parent_name: we are using the name of an element
                 or module in its definition, no need of cross_reference
                 - if the path of name is a suffix of the parent path, we
                 are in the same module, maybe the same function. To decrease
                 the false positive rate, we stop here *)
              if name = parent_name || is_path_suffix () then
                t_ele
              else
                let finalize = function
                  | None -> t_ele
                  | Some _ ->
                      Odoc_global.pwarning @@
                      Odoc_messages.code_could_be_cross_reference name parent_name;
                      t_ele in
                search_within_ancestry finalize (query module_list) ~parent_name
                  name
      end
  | Ref (initial_name, Some kind, text_option) ->
      (
       let rec iter_parent ?parent_name name =
         let v = (name, Some kind) in
         if was_verified v then
           Ref (name, Some kind, text_option)
         else
           let res =
             match kind with
             | RK_section _ ->
                 (
                  (* we just verify that we find an element of this kind with this name *)
                  try
                    let re = Str.regexp ("^"^(Str.quote name)^"$") in
                    let t = Odoc_search.find_section module_list re in
                    let v2 = (name, Some (RK_section t)) in
                    add_verified v2 ;
                    (name, Some (RK_section t))
                  with
                    Not_found ->
                      (name, None)
                 )
             | _ ->
                 let f =
                   match kind with
                     RK_module -> module_exists
                   | RK_module_type -> module_type_exists
                   | RK_class -> class_exists
                   | RK_class_type -> class_type_exists
                   | RK_value -> value_exists
                   | RK_type -> type_exists
                   | RK_extension -> extension_exists
                   | RK_exception -> exception_exists
                   | RK_attribute -> attribute_exists
                   | RK_method -> method_exists
                   | RK_section _ -> assert false
                   | RK_recfield -> recfield_exists
                   | RK_const -> const_exists
                 in
                 if f name then
                   (
                    add_verified v ;
                    (name, Some kind)
                   )
                 else
                   (name, None)
           in
           match res with
           | (name, Some k) -> Ref (name, Some k, text_option)
           | (_, None) ->
               match parent_name with
                 None ->
                   Odoc_global.pwarning (not_found_of_kind kind initial_name);
                   Ref (initial_name, None, text_option)
               | Some p ->
                   let parent_name =
                     match Name.father p with
                       "" -> None
                     | s -> Some s
                   in
                   iter_parent ?parent_name (Name.concat p initial_name)
       in
       iter_parent ~parent_name initial_name
      )
  | Module_list l ->
      Module_list l
  | Index_list ->
      Index_list
  | Custom (s,t) -> Custom (s, (assoc_comments_text parent_name module_list t))
  | Target (target, code) -> Target (target, code)

and assoc_comments_text parent_name module_list text =
  List.map (assoc_comments_text_elements parent_name module_list) text

and assoc_comments_info parent_name module_list i =
  let ft = assoc_comments_text parent_name module_list in
  {
    i with
    i_desc = ao ft i.i_desc ;
    i_sees = List.map (fun (sr, t) -> (sr, ft t)) i.i_sees;
    i_deprecated = ao ft i.i_deprecated ;
    i_params = List.map (fun (name, t) -> (name, ft t)) i.i_params;
    i_raised_exceptions = List.map (fun (name, t) -> (name, ft t)) i.i_raised_exceptions;
    i_return_value = ao ft i.i_return_value ;
    i_custom = List.map (fun (tag, t) -> (tag, ft t)) i.i_custom ;
  }


let rec assoc_comments_module_element parent_name module_list m_ele =
  match m_ele with
    Element_module m ->
      Element_module (assoc_comments_module module_list m)
  | Element_module_type mt ->
      Element_module_type (assoc_comments_module_type module_list mt)
  | Element_included_module _ ->
      m_ele (* don't go down into the aliases *)
  | Element_class c ->
      Element_class (assoc_comments_class module_list c)
  | Element_class_type ct ->
      Element_class_type (assoc_comments_class_type module_list ct)
  | Element_value v ->
      Element_value (assoc_comments_value module_list v)
  | Element_type_extension te ->
      Element_type_extension (assoc_comments_type_extension parent_name module_list te)
  | Element_exception e ->
      Element_exception (assoc_comments_exception module_list e)
  | Element_type t ->
      Element_type (assoc_comments_type module_list t)
  | Element_module_comment t ->
      Element_module_comment (assoc_comments_text parent_name module_list t)

and assoc_comments_class_element parent_name module_list c_ele =
  match c_ele with
    Class_attribute a ->
      Class_attribute (assoc_comments_attribute module_list a)
  | Class_method m ->
      Class_method (assoc_comments_method module_list m)
  | Class_comment t ->
      Class_comment (assoc_comments_text parent_name module_list t)

and assoc_comments_module_kind parent_name module_list mk =
  match mk with
  | Module_struct eles ->
      Module_struct
        (List.map (assoc_comments_module_element parent_name module_list) eles)
  | Module_alias _
  | Module_functor _ ->
      mk
  | Module_apply (mk1, mk2) ->
      Module_apply
        (assoc_comments_module_kind parent_name module_list mk1,
         assoc_comments_module_kind parent_name module_list mk2)
  | Module_apply_unit mk1 ->
      Module_apply_unit
        (assoc_comments_module_kind parent_name module_list mk1)
  | Module_with (mtk, s) ->
      Module_with (assoc_comments_module_type_kind parent_name module_list mtk, s)
  | Module_constraint (mk1, mtk) ->
      Module_constraint
        (assoc_comments_module_kind parent_name module_list mk1,
         assoc_comments_module_type_kind parent_name module_list mtk)
  | Module_typeof _ -> mk
  | Module_unpack _ -> mk

and assoc_comments_module_type_kind parent_name module_list mtk =
  match mtk with
  | Module_type_struct eles ->
      Module_type_struct
        (List.map (assoc_comments_module_element parent_name module_list) eles)
  | Module_type_functor (params, mtk1) ->
      Module_type_functor
        (params, assoc_comments_module_type_kind parent_name module_list mtk1)
  | Module_type_alias _ ->
      mtk
  | Module_type_with (mtk1, s) ->
      Module_type_with
        (assoc_comments_module_type_kind parent_name module_list mtk1, s)
  | Module_type_typeof _ -> mtk

and assoc_comments_class_kind parent_name module_list ck =
  match ck with
    Class_structure (inher, eles) ->
      let inher2 =
        List.map
          (fun ic ->
            { ic with
              ic_text = ao (assoc_comments_text parent_name module_list) ic.ic_text })
          inher
      in
      Class_structure
        (inher2, List.map (assoc_comments_class_element parent_name module_list) eles)

  | Class_apply _
  | Class_constr _ -> ck
  | Class_constraint (ck1, ctk) ->
      Class_constraint (assoc_comments_class_kind parent_name module_list ck1,
                        assoc_comments_class_type_kind parent_name module_list ctk)

and assoc_comments_class_type_kind parent_name module_list ctk =
  match ctk with
    Class_signature (inher, eles) ->
      let inher2 =
        List.map
          (fun ic -> { ic with
                       ic_text = ao (assoc_comments_text parent_name module_list) ic.ic_text })
          inher
      in
      Class_signature (inher2, List.map (assoc_comments_class_element parent_name module_list) eles)

  | Class_type _ -> ctk


and assoc_comments_module module_list m =
  m.m_info <- ao (assoc_comments_info m.m_name module_list) m.m_info ;
  m.m_kind <- assoc_comments_module_kind m.m_name module_list m.m_kind ;
  m

and assoc_comments_module_type module_list mt =
  mt.mt_info <- ao (assoc_comments_info mt.mt_name module_list) mt.mt_info ;
  mt.mt_kind <- ao (assoc_comments_module_type_kind mt.mt_name module_list) mt.mt_kind ;
  mt

and assoc_comments_class module_list c =
  c.cl_info <- ao (assoc_comments_info c.cl_name module_list) c.cl_info ;
  c.cl_kind <- assoc_comments_class_kind c.cl_name module_list c.cl_kind ;
  assoc_comments_parameter_list c.cl_name module_list c.cl_parameters;
  c

and assoc_comments_class_type module_list ct =
  ct.clt_info <- ao (assoc_comments_info ct.clt_name module_list) ct.clt_info ;
  ct.clt_kind <- assoc_comments_class_type_kind ct.clt_name module_list ct.clt_kind ;
  ct

and assoc_comments_parameter parent_name module_list p =
  match p with
    Simple_name sn ->
      sn.sn_text <- ao (assoc_comments_text parent_name module_list) sn.sn_text
  | Tuple (l, _) ->
      List.iter (assoc_comments_parameter parent_name module_list) l

and assoc_comments_parameter_list parent_name module_list pl =
  List.iter (assoc_comments_parameter parent_name module_list) pl

and assoc_comments_value module_list v =
  let parent = Name.father v.val_name in
  v.val_info <- ao (assoc_comments_info parent module_list) v.val_info ;
  assoc_comments_parameter_list parent module_list v.val_parameters;
  v

and assoc_comments_extension_constructor module_list x =
  let parent = Name.father x.xt_name in
  x.xt_text <- ao (assoc_comments_info parent module_list) x.xt_text

and assoc_comments_type_extension parent_name module_list te =
  te.te_info <- ao (assoc_comments_info parent_name module_list) te.te_info;
  List.iter (assoc_comments_extension_constructor module_list) te.te_constructors;
  te

and assoc_comments_exception module_list e =
  let parent = Name.father e.ex_name in
  e.ex_info <- ao (assoc_comments_info parent module_list) e.ex_info ;
  e

and assoc_comments_type module_list t =
  let parent = Name.father t.ty_name in
  t.ty_info <- ao (assoc_comments_info parent module_list) t.ty_info ;
  (match t.ty_kind with
    Type_abstract -> ()
  | Type_variant vl ->
      List.iter
        (fun vc -> vc.vc_text <- ao (assoc_comments_info parent module_list) vc.vc_text)
        vl
  | Type_record fl ->
      List.iter
        (fun rf -> rf.rf_text <- ao (assoc_comments_info parent module_list) rf.rf_text)
        fl
  | Type_open -> ()
  );
  t

and assoc_comments_attribute module_list a =
  let _ = assoc_comments_value module_list a.att_value in
  a

and assoc_comments_method module_list m =
  let parent_name = Name.father m.met_value.val_name in
  let _ = assoc_comments_value module_list m.met_value in
  assoc_comments_parameter_list parent_name module_list m.met_value.val_parameters;
  m


let associate_type_of_elements_in_comments module_list =
  List.map (assoc_comments_module module_list) module_list


(***********************************************************)
(** The function which performs all the cross referencing. *)
let associate module_list =
  get_alias_names module_list ;
  init_known_elements_map module_list;
  let rec remove_doubles acc = function
      [] -> acc
    | h :: q ->
        if List.mem h acc then remove_doubles acc q
        else remove_doubles (h :: acc) q
  in
  let rec iter incomplete_modules =
    let (b_modif, remaining_inc_modules, acc_names_not_found) =
      List.fold_left (associate_in_module module_list) (false, [], []) incomplete_modules
    in
    let remaining_no_doubles = remove_doubles [] remaining_inc_modules in
    let remaining_modules = List.filter
        (fun m -> List.mem m.m_name remaining_no_doubles)
        incomplete_modules
    in
    if b_modif then
      (* we may be able to associate something else *)
      iter remaining_modules
    else
      (* nothing changed, we won't be able to associate any more *)
      acc_names_not_found
  in
  let names_not_found = iter module_list in
  (
   match names_not_found with
     [] ->
       ()
   | l ->
       List.iter
         (fun nf ->
           Odoc_global.pwarning
             (
              match nf with
              | NF_mt n -> Odoc_messages.cross_module_type_not_found n
              | NF_mmt n -> Odoc_messages.cross_module_or_module_type_not_found n
              | NF_c n -> Odoc_messages.cross_class_not_found n
              | NF_cct n -> Odoc_messages.cross_class_or_class_type_not_found n
              | NF_xt n -> Odoc_messages.cross_extension_not_found n
              | NF_ex n -> Odoc_messages.cross_exception_not_found n
             );
         )
         l
  ) ;

  (* Find a type for each name of element which is referenced in comments. *)
  ignore (associate_type_of_elements_in_comments module_list)
