(***********************************************************************)
(*                                                                     *)
(*                                OCaml                                *)
(*                                                                     *)
(*                  Fabrice Le Fessant, INRIA Saclay                   *)
(*                                                                     *)
(*  Copyright 2012 Institut National de Recherche en Informatique et   *)
(*  en Automatique.  All rights reserved.  This file is distributed    *)
(*  under the terms of the Q Public License version 1.0.               *)
(*                                                                     *)
(***********************************************************************)

(* Generate an .annot file from a .cmt file. *)

open Asttypes
open Typedtree

let bind_variables scope =
  object
    inherit Tast_iter.iter as super

    method! pattern pat =
      super # pattern pat;
      match pat.pat_desc with
      | Tpat_var (id, _) | Tpat_alias (_, id, _) ->
          Stypes.record (Stypes.An_ident (pat.pat_loc,
                                          Ident.name id,
                                          Annot.Idef scope))
      | _ -> ()
  end

let bind_bindings scope bindings =
  let o = bind_variables scope in
  List.iter (fun (p, _) -> o # pattern p) bindings

let bind_cases l =
  List.iter (fun (p, e) -> (bind_variables e.exp_loc) # pattern p) l

let iterator rebuild_env =
  object(this)
    val scope = Location.none  (* scope of the surrounding structure *)

    inherit Tast_iter.iter as super

    method! class_expr node =
      Stypes.record (Stypes.Ti_class node);
      super # class_expr node

    method! module_expr node =
      Stypes.record (Stypes.Ti_mod node);
      Tast_iter.module_expr {< scope = node.mod_loc >} node

    method! expression exp =
      begin match exp.exp_desc with
      | Texp_ident (path, _, _) ->
          let full_name = Path.name ~paren:Oprint.parenthesized_ident path in
          let env =
            if rebuild_env then
              try
                Env.env_of_only_summary Envaux.env_from_summary exp.exp_env
              with Envaux.Error err ->
                Format.eprintf "%a@." Envaux.report_error err;
                exit 2
            else
              exp.exp_env
          in
          let annot =
            try
              let desc = Env.find_value path env in
              let dloc = desc.Types.val_loc in
              if dloc.Location.loc_ghost then Annot.Iref_external
              else Annot.Iref_internal dloc
            with Not_found ->
              Annot.Iref_external
          in
          Stypes.record
            (Stypes.An_ident (exp.exp_loc, full_name , annot))
      | Texp_let (Recursive, bindings, _) ->
          bind_bindings exp.exp_loc bindings
      | Texp_let (Nonrecursive, bindings, body) ->
          bind_bindings body.exp_loc bindings
      | Texp_function (_, f, _)
      | Texp_match (_, f, _)
      | Texp_try (_, f) ->
          bind_cases f
      | _ -> ()
      end;
      Stypes.record (Stypes.Ti_expr exp);
      super # expression exp

    method! pattern pat =
      super # pattern pat;
      Stypes.record (Stypes.Ti_pat pat)

    method private structure_item_rem s rem =
      begin match s with
      | {str_desc = Tstr_value (rec_flag, bindings); str_loc = loc} ->
          let open Location in
          let doit loc_start = bind_bindings {scope with loc_start} bindings in
          begin match rec_flag, rem with
          | Default, _ -> ()
          | Recursive, _ -> doit loc.loc_start
          | Nonrecursive, [] -> doit loc.loc_end
          | Nonrecursive,  {str_loc = loc2} :: _ -> doit loc2.loc_start
          end
      | _ ->
          ()
      end;
      Stypes.record_phrase s.str_loc;
      super # structure_item s

    method! structure_item s =
      (* This will be used for Partial_structure_item.
         We don't have here the location of the "next" item,
         this will give a slightly different scope for the non-recursive
         binding case. *)
      this # structure_item_rem s []

    method! structure l =
      let rec loop = function
        | str :: rem -> this # structure_item_rem str rem; loop rem
        | [] -> ()
      in
      loop l.str_items

(* TODO: support binding for Tcl_fun, Tcl_let, etc *)
  end

let binary_part iter x =
  let open Cmt_format in
  match x with
  | Partial_structure x -> iter # structure x
  | Partial_structure_item x -> iter # structure_item x
  | Partial_expression x -> iter # expression x
  | Partial_pattern x -> iter # pattern x
  | Partial_class_expr x -> iter # class_expr x
  | Partial_signature x -> iter # signature x
  | Partial_signature_item x -> iter # signature_item x
  | Partial_module_type x -> iter # module_type x

let gen_annot target_filename filename
              {Cmt_format.cmt_loadpath; cmt_annots; cmt_use_summaries; _} =
  let open Cmt_format in
  Envaux.reset_cache ();
  Config.load_path := cmt_loadpath;
  let target_filename =
    match target_filename with
    | None -> Some (filename ^ ".annot")
    | Some "-" -> None
    | Some filename -> target_filename
  in
  let iterator = iterator cmt_use_summaries in
  match cmt_annots with
  | Implementation typedtree ->
      iterator # structure typedtree;
      Stypes.dump target_filename
  | Interface _ ->
      Printf.eprintf "Cannot generate annotations for interface file\n%!";
      exit 2
  | Partial_implementation parts ->
      Array.iter (binary_part iterator) parts;
      Stypes.dump target_filename
  | _ ->
      Printf.fprintf stderr "File was generated with an error\n%!";
      exit 2



let gen_ml target_filename filename cmt =
  let (printer, ext) =
    match cmt.Cmt_format.cmt_annots with
      | Cmt_format.Implementation typedtree ->
        (fun ppf -> Pprintast.structure ppf (Untypeast.untype_structure typedtree)), ".ml"
      | Cmt_format.Interface typedtree ->
        (fun ppf -> Pprintast.signature ppf (Untypeast.untype_signature typedtree)), ".mli"
      | _ ->
        Printf.fprintf stderr "File was generated with an error\n%!";
        exit 2
  in
  let target_filename = match target_filename with
      None -> Some (filename ^ ext)
    | Some "-" -> None
    | Some filename -> target_filename
  in
  let oc = match target_filename with
      None -> None
    | Some filename -> Some (open_out filename) in
  let ppf = match oc with
      None -> Format.std_formatter
    | Some oc -> Format.formatter_of_out_channel oc in
  printer ppf;
  Format.pp_print_flush ppf ();
  match oc with
      None -> flush stdout
    | Some oc -> close_out oc
