(**************************************************************************)
(*                                                                        *)
(*                                 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.          *)
(*                                                                        *)
(**************************************************************************)

(** Research of elements through modules. *)

open Odoc_value
open Odoc_type
open Odoc_extension
open Odoc_exception
open Odoc_class
open Odoc_module

type result_element =
    Res_module of t_module
  | Res_module_type of t_module_type
  | Res_class of t_class
  | Res_class_type of t_class_type
  | Res_value of t_value
  | Res_type of t_type
  | Res_extension of t_extension_constructor
  | Res_exception of t_exception
  | Res_attribute of t_attribute
  | Res_method of t_method
  | Res_section of string * Odoc_types.text
  | Res_recfield of t_type * record_field
  | Res_const of t_type * variant_constructor

type result = result_element list

module type Predicates =
  sig
    type t
    val p_module : t_module -> t -> bool * bool
    val p_module_type : t_module_type -> t -> bool * bool
    val p_class : t_class -> t -> bool * bool
    val p_class_type : t_class_type -> t -> bool * bool
    val p_value : t_value -> t -> bool
    val p_recfield : t_type -> record_field -> t -> bool
    val p_const : t_type -> variant_constructor -> t -> bool
    val p_type : t_type -> t -> (bool * bool)
    val p_extension : t_extension_constructor -> t -> bool
    val p_exception : t_exception -> t -> bool
    val p_attribute : t_attribute -> t -> bool
    val p_method : t_method -> t -> bool
    val p_section : string -> t -> bool
  end

module Search =
  functor (P : Predicates) ->
  struct
    let search_section t s v = if P.p_section s v then [Res_section (s,t)] else []

    let rec search_text root t v =
      List.flatten (List.map (fun e -> search_text_ele root e v) t)

    and search_text_ele root e v =
      let module T = Odoc_types in
      match e with
      | T.Raw _
      | T.Code _
      | T.CodePre _
      | T.Latex _
      | T.Verbatim _
      | T.Ref (_, _, _) -> []
      | T.Bold t
      | T.Italic t
      | T.Center t
      | T.Left t
      | T.Right t
      | T.Emphasize t
      | T.Block t
      | T.Superscript t
      | T.Subscript t
      | T.Custom (_,t)
      | T.Link (_, t) -> search_text root t v
      | T.List l
      | T.Enum l -> List.flatten (List.map (fun t -> search_text root t v) l)
      | T.Newline
      | T.Module_list _
      | T.Index_list -> []
      | T.Target _ -> []
      | T.Title (_, l_opt, t) ->
          (match l_opt with
            None -> []
          | Some s -> search_section t (Name.concat root s) v) @
          (search_text root t v)

    let search_value va v = if P.p_value va v then [Res_value va] else []

    let search_recfield t f v =
      if P.p_recfield t f v then [Res_recfield (t,f)] else []

    let search_const t f v =
      if P.p_const t f v then [Res_const (t,f)] else []

    let search_type t v =
      let (go_deeper, ok) = P.p_type t v in
      let l =
        match go_deeper with
          false -> []
        | true ->
            match t.ty_kind with
              Type_abstract -> []
            | Type_record l ->
                List.flatten (List.map (fun rf -> search_recfield t rf v) l)
            | Type_variant l ->
                List.flatten (List.map (fun rf -> search_const t rf v) l)
            | Type_open -> []
            | Type_external _ -> []
      in
      if ok then (Res_type t) :: l else l

    let search_extension_constructor xt v =
      if P.p_extension xt v then [Res_extension xt] else []

    let search_type_extension te v =
      List.fold_left
        (fun acc -> fun xt -> acc @ (search_extension_constructor xt v))
        []
        (Odoc_extension.extension_constructors te)

    let search_exception e v = if P.p_exception e v then [Res_exception e] else []

    let search_attribute a v = if P.p_attribute a v then [Res_attribute a] else []

    let search_method m v = if P.p_method m v then [Res_method m] else []

    let search_class c v =
      let (go_deeper, ok) = P.p_class c v in
      let l =
        if go_deeper then
          let res_att =
            List.fold_left
              (fun acc -> fun att -> acc @ (search_attribute att v))
              []
              (Odoc_class.class_attributes c)
          in
          let res_met =
            List.fold_left
              (fun acc -> fun m -> acc @ (search_method m v))
              []
              (Odoc_class.class_methods c)
          in
          let res_sec =
            List.fold_left
              (fun acc -> fun t -> acc @ (search_text c.cl_name t v))
              []
              (Odoc_class.class_comments c)
          in
          res_att @ res_met @ res_sec
        else
          []
      in
      if ok then
        (Res_class c) :: l
      else
        l

    let search_class_type ct v =
      let (go_deeper, ok) = P.p_class_type ct v in
      let l =
        if go_deeper then
          let res_att =
            List.fold_left
              (fun acc -> fun att -> acc @ (search_attribute att v))
              []
              (Odoc_class.class_type_attributes ct)
          in
          let res_met =
            List.fold_left
              (fun acc -> fun m -> acc @ (search_method m v))
              []
              (Odoc_class.class_type_methods ct)
          in
          let res_sec =
            List.fold_left
              (fun acc -> fun t -> acc @ (search_text ct.clt_name t v))
              []
              (Odoc_class.class_type_comments ct)
          in
          res_att @ res_met @ res_sec
        else
          []
      in
      if ok then
        (Res_class_type ct) :: l
      else
        l

    let rec search_module_type mt v =
      let (go_deeper, ok) =  P.p_module_type mt v in
      let l =
        if go_deeper then
          let res_val =
            List.fold_left
              (fun acc -> fun va -> acc @ (search_value va v))
              []
              (Odoc_module.module_type_values mt)
          in
          let res_typ =
            List.fold_left
              (fun acc -> fun t -> acc @ (search_type t v))
              []
              (Odoc_module.module_type_types mt)
          in
          let res_ext =
            List.fold_left
              (fun acc -> fun te -> acc @ (search_type_extension te v))
              []
              (Odoc_module.module_type_type_extensions mt)
          in
          let res_exc =
            List.fold_left
              (fun acc -> fun e -> acc @ (search_exception e v))
              []
              (Odoc_module.module_type_exceptions mt)
          in
          let res_mod = search (Odoc_module.module_type_modules mt) v in
          let res_modtyp =
            List.fold_left
              (fun acc -> fun mt -> acc @ (search_module_type mt v))
              []
              (Odoc_module.module_type_module_types mt)
          in
          let res_cl =
            List.fold_left
              (fun acc -> fun cl -> acc @ (search_class cl v))
              []
              (Odoc_module.module_type_classes mt)
          in
          let res_cltyp =
            List.fold_left
              (fun acc -> fun clt -> acc @ (search_class_type clt v))
              []
              (Odoc_module.module_type_class_types mt)
          in
          let res_sec =
            List.fold_left
              (fun acc -> fun t -> acc @ (search_text mt.mt_name t v))
              []
              (Odoc_module.module_type_comments mt)
          in
          res_val @ res_typ @ res_ext @ res_exc @ res_mod @
            res_modtyp @ res_cl @ res_cltyp @ res_sec
        else
          []
      in
      if ok then
        (Res_module_type mt) :: l
      else
        l

    and search_module m v =
      let (go_deeper, ok) =  P.p_module m v in
      let l =
        if go_deeper then
          let res_val =
            List.fold_left
              (fun acc -> fun va -> acc @ (search_value va v))
              []
              (Odoc_module.module_values m)
          in
          let res_typ =
            List.fold_left
              (fun acc -> fun t -> acc @ (search_type t v))
              []
              (Odoc_module.module_types m)
          in
          let res_ext =
            List.fold_left
              (fun acc -> fun te -> acc @ (search_type_extension te v))
              []
              (Odoc_module.module_type_extensions m)
          in
          let res_exc =
            List.fold_left
              (fun acc -> fun e -> acc @ (search_exception e v))
              []
              (Odoc_module.module_exceptions m)
          in
          let res_mod = search (Odoc_module.module_modules m) v in
          let res_modtyp =
            List.fold_left
              (fun acc -> fun mt -> acc @ (search_module_type mt v))
              []
              (Odoc_module.module_module_types m)
          in
          let res_cl =
            List.fold_left
              (fun acc -> fun cl -> acc @ (search_class cl v))
              []
              (Odoc_module.module_classes m)
          in
          let res_cltyp =
            List.fold_left
              (fun acc -> fun clt -> acc @ (search_class_type clt v))
              []
              (Odoc_module.module_class_types m)
          in
          let res_sec =
            List.fold_left
              (fun acc -> fun t -> acc @ (search_text m.m_name t v))
              []
              (Odoc_module.module_comments m)
          in
          res_val @ res_typ @ res_ext @ res_exc @ res_mod @
            res_modtyp @ res_cl @ res_cltyp @ res_sec
        else
          []
      in
      if ok then
        (Res_module m) :: l
      else
        l

    and search module_list v =
      let results_with_duplicates =
        List.fold_left
          (fun rev_acc m ->
            List.rev_append (search_module m v) rev_acc)
          [] module_list
        |> List.rev
      in
      Odoc_misc.remove_duplicates Stdlib.compare results_with_duplicates
  end

module P_name =
  struct
    type t = Str.regexp
    let (=~) name regexp = Str.string_match regexp name 0
    let p_module m r = (true, m.m_name =~ r)
    let p_module_type mt r = (true, mt.mt_name =~ r)
    let p_class c r = (true, c.cl_name =~ r)
    let p_class_type ct r = (true, ct.clt_name =~ r)
    let p_value v r = v.val_name =~ r
    let p_recfield t f r =
      let name = Printf.sprintf "%s.%s" t.ty_name f.rf_name in
      name =~ r
    let p_const t f r =
      let name = Printf.sprintf "%s.%s" t.ty_name f.vc_name in
      name =~ r
    let p_type t r = (true, t.ty_name =~ r)
    let p_extension x r = x.xt_name =~ r
    let p_exception e r = e.ex_name =~ r
    let p_attribute a r = a.att_value.val_name =~ r
    let p_method m r = m.met_value.val_name =~ r
    let p_section s r = s =~ r
  end

module Search_by_name = Search ( P_name )

module P_values =
  struct
    type t = unit
    let p_module _ _ = (true, false)
    let p_module_type _ _ = (true, false)
    let p_class _ _ = (false, false)
    let p_class_type _ _ = (false, false)
    let p_value _ _ = true
    let p_recfield _ _ _ = false
    let p_const _ _ _ = false
    let p_type _ _ = (false, false)
    let p_extension _ _ = false
    let p_exception _ _ = false
    let p_attribute _ _ = false
    let p_method _ _ = false
    let p_section _ _ = false
  end
module Search_values = Search ( P_values )
let values l =
  let l_ele = Search_values.search l () in
  let p v1 v2 = v1.val_name = v2.val_name in
  let rec iter acc = function
      (Res_value v) :: q -> if List.exists (p v) acc then iter acc q else iter (v :: acc) q
    | _ :: q -> iter acc q
    | [] -> acc
  in
  iter [] l_ele

module P_extensions =
  struct
    type t = unit
    let p_module _ _ = (true, false)
    let p_module_type _ _ = (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 _ _ = true
    let p_exception _ _ = false
    let p_attribute _ _ = false
    let p_method _ _ = false
    let p_section _ _ = false
  end
module Search_extensions = Search ( P_extensions )
let extensions l =
  let l_ele = Search_extensions.search l () in
  let p x1 x2 = x1.xt_name = x2.xt_name in
  let rec iter acc = function
      (Res_extension x) :: q -> if List.exists (p x) acc then iter acc q else iter (x :: acc) q
    | _ :: q -> iter acc q
    | [] -> acc
  in
  iter [] l_ele

module P_exceptions =
  struct
    type t = unit
    let p_module _ _ = (true, false)
    let p_module_type _ _ = (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 _ _ = false
    let p_exception _ _ = true
    let p_attribute _ _ = false
    let p_method _ _ = false
    let p_section _ _ = false
  end
module Search_exceptions = Search ( P_exceptions )
let exceptions l =
  let l_ele = Search_exceptions.search l () in
  let p e1 e2 = e1.ex_name = e2.ex_name in
  let rec iter acc = function
      (Res_exception t) :: q -> if List.exists (p t) acc then iter acc q else iter (t :: acc) q
    | _ :: q -> iter acc q
    | [] -> acc
  in
  iter [] l_ele

module P_types =
  struct
    type t = unit
    let p_module _ _ = (true, false)
    let p_module_type _ _ = (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, true)
    let p_extension _ _ = false
    let p_exception _ _ = false
    let p_attribute _ _ = false
    let p_method _ _ = false
    let p_section _ _ = false
  end
module Search_types = Search ( P_types )
let types l =
  let l_ele = Search_types.search l () in
  let p t1 t2 = t1.ty_name = t2.ty_name in
  let rec iter acc = function
      (Res_type t) :: q -> if List.exists (p t) acc then iter acc q else iter (t :: acc) q
    | _ :: q -> iter acc q
    | [] -> acc
  in
  iter [] l_ele

module P_attributes =
  struct
    type t = unit
    let p_module _ _ = (true, false)
    let p_module_type _ _ = (true, false)
    let p_class _ _ = (true, false)
    let p_class_type _ _ = (true, false)
    let p_value _ _ = false
    let p_recfield _ _ _ = false
    let p_const _ _ _ = false
    let p_type _ _ = (false, false)
    let p_extension _ _ = false
    let p_exception _ _ = false
    let p_attribute _ _ = true
    let p_method _ _ = false
    let p_section _ _ = false
  end
module Search_attributes = Search ( P_attributes )
let attributes l =
  let l_ele = Search_attributes.search l () in
  let p a1 a2 = a1.att_value.val_name = a2.att_value.val_name in
  let rec iter acc = function
      (Res_attribute t) :: q -> if List.exists (p t) acc then iter acc q else iter (t :: acc) q
    | _ :: q -> iter acc q
    | [] -> acc
  in
  iter [] l_ele

module P_methods =
  struct
    type t = unit
    let p_module _ _ = (true, false)
    let p_module_type _ _ = (true, false)
    let p_class _ _ = (true, false)
    let p_class_type _ _ = (true, false)
    let p_value _ _ = false
    let p_recfield _ _ _ = false
    let p_const _ _ _ = false
    let p_type _ _ = (false, false)
    let p_extension _ _ = false
    let p_exception _ _ = false
    let p_attribute _ _ = false
    let p_method _ _ = true
    let p_section _ _ = true
  end
module Search_methods = Search ( P_methods )
let methods l =
  let l_ele = Search_methods.search l () in
  let p m1 m2 = m1.met_value.val_name = m2.met_value.val_name in
  let rec iter acc = function
      (Res_method t) :: q -> if List.exists (p t) acc then iter acc q else iter (t :: acc) q
    | _ :: q -> iter acc q
    | [] -> acc
  in
  iter [] l_ele

module P_classes =
  struct
    type t = unit
    let p_module _ _ = (true, false)
    let p_module_type _ _ = (true, false)
    let p_class _ _ = (false, true)
    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 _ _ = false
    let p_exception _ _ = false
    let p_attribute _ _ = false
    let p_method _ _ = false
    let p_section _ _ = false
  end
module Search_classes = Search ( P_classes )
let classes l =
  let l_ele = Search_classes.search l () in
  let p c1 c2 = c1.cl_name = c2.cl_name in
  let rec iter acc = function
      (Res_class c) :: q -> if List.exists (p c) acc then iter acc q else iter (c :: acc) q
    | _ :: q -> iter acc q
    | [] -> acc
  in
  iter [] l_ele

module P_class_types =
  struct
    type t = unit
    let p_module _ _ = (true, false)
    let p_module_type _ _ = (true, false)
    let p_class _ _ = (false, false)
    let p_class_type _ _ = (false, true)
    let p_value _ _ = false
    let p_recfield _ _ _ = false
    let p_const _ _ _ = false
    let p_type _ _ = (false, false)
    let p_extension _ _ = false
    let p_exception _ _ = false
    let p_attribute _ _ = false
    let p_method _ _ = false
    let p_section _ _ = false
  end
module Search_class_types = Search ( P_class_types )
let class_types l =
  let l_ele = Search_class_types.search l () in
  let p c1 c2 = c1.clt_name = c2.clt_name in
  let rec iter acc = function
      (Res_class_type c) :: q -> if List.exists (p c) acc then iter acc q else iter (c :: acc) q
    | _ :: q -> iter acc q
    | [] -> acc
  in
  iter [] l_ele

module P_modules =
  struct
    type t = unit
    let p_module _ _ = (true, true)
    let p_module_type _ _ = (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 _ _ = false
    let p_exception _ _ = false
    let p_attribute _ _ = false
    let p_method _ _ = false
    let p_section _ _ = false
  end
module Search_modules = Search ( P_modules )
let modules l =
  let l_ele = Search_modules.search l () in
  let p m1 m2 = m1.m_name = m2.m_name in
  let rec iter acc = function
      (Res_module m) :: q -> if List.exists (p m) acc then iter acc q else iter (m :: acc) q
    | _ :: q -> iter acc q
    | [] -> acc
  in
  iter [] l_ele

module P_module_types =
  struct
    type t = unit
    let p_module _ _ = (true, false)
    let p_module_type _ _ = (true, true)
    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 _ _ = false
    let p_exception _ _ = false
    let p_attribute _ _ = false
    let p_method _ _ = false
    let p_section _ _ = false
  end
module Search_module_types = Search ( P_module_types )
let module_types l =
  let l_ele = Search_module_types.search l () in
  let p m1 m2 = m1.mt_name = m2.mt_name in
  let rec iter acc = function
      (Res_module_type m) :: q -> if List.exists (p m) acc then iter acc q else iter (m :: acc) q
    | _ :: q -> iter acc q
    | [] -> acc
  in
  iter [] l_ele

let type_exists mods regexp =
  let l = Search_by_name.search mods regexp in
  List.exists
    (function
        Res_type _ -> true
      | _ -> false
    )
    l

let value_exists mods regexp =
  let l = Search_by_name.search mods regexp in
  List.exists
    (function
        Res_value _ -> true
      | _ -> false
    )
    l

let class_exists mods regexp =
  let l = Search_by_name.search mods regexp in
  List.exists
    (function
        Res_class _ -> true
      | _ -> false
    )
    l

let class_type_exists mods regexp =
  let l = Search_by_name.search mods regexp in
  List.exists
    (function
        Res_class_type _ -> true
      | _ -> false
    )
    l

let module_exists mods regexp =
  let l = Search_by_name.search mods regexp in
  List.exists
    (function
        Res_module _ -> true
      | _ -> false
    )
    l

let module_type_exists mods regexp =
  let l = Search_by_name.search mods regexp in
  List.exists
    (function
        Res_module_type _ -> true
      | _ -> false
    )
    l

let extension_exists mods regexp =
  let l = Search_by_name.search mods regexp in
  List.exists
    (function
        Res_extension _ -> true
      | _ -> false
    )
    l

let exception_exists mods regexp =
  let l = Search_by_name.search mods regexp in
  List.exists
    (function
        Res_exception _ -> true
      | _ -> false
    )
    l

let attribute_exists mods regexp =
  let l = Search_by_name.search mods regexp in
  List.exists
    (function
        Res_attribute _ -> true
      | _ -> false
    )
    l

let method_exists mods regexp =
  let l = Search_by_name.search mods regexp in
  List.exists
    (function
        Res_method _ -> true
      | _ -> false
    )
    l

let find_section mods regexp =
  let l = Search_by_name.search mods regexp in
  match
    List.find
      (function
          Res_section _ -> true
        | _ -> false
      )
      l
  with
    Res_section (_,t) -> t
  | _ -> assert false
