(* Time-stamp: <modified the 10/05/2013 (at 15:00) by Erwan Jahier> *)

open Verbose
open AstV6
open AstCore
open Lxm
open Lv6errors
open Parsing
open Format

open Lv6MainArgs
let my_exit opt i =
  close_out opt.oc;
  if Sys.file_exists opt.outfile then Sys.remove opt.outfile;
  exit i

let rec first_pack_in =
  function
    | (AstV6.NSPack pi)::_ -> pi.it.pa_name
    | (AstV6.NSModel _)::tail -> first_pack_in tail
    | [] -> raise (Global_error "No package has been provided")

(* try to use the filenale to guess the dft node *)
let find_a_node opt =
  let first_file = List.hd opt.infiles in
  let name = 
    try Filename.chop_extension (Filename.basename first_file) 
    with _ -> 
		print_string ("*** '"^first_file^"': bad file name.\n"); exit 1
  in
  name


(* Generates a lutin env and a lustre oracle for the node *)
let (gen_autotest_files : LicPrg.t -> Ident.idref option -> Lv6MainArgs.t -> unit) =
  fun lic_prg main_node opt -> 
    let msk, zesoc, main_node = 
      match main_node with
        | None -> (
          let name = find_a_node opt in
          let main_node = Ident.to_idref name in
          let nk = (Lic.node_key_of_idref main_node) in
          if LicPrg.node_exists lic_prg nk then (
            output_string stdout ("WARNING: No main node is specified. I'll try with " ^ name ^"\n");
            flush stdout;
            let msk, zesoc = Lic2soc.f lic_prg nk in
            msk, zesoc, main_node
          ) else (
            print_string ("Error: no node is specified, cannot exec.\n");
            flush stdout;
            exit 1
          )
        )
        | Some main_node -> 
          let msk, zesoc = Lic2soc.f lic_prg (Lic.node_key_of_idref main_node) in
          msk, zesoc, main_node
    in
    let my_type_to_string range_flag t =
      (* Remove the module name to have correct Lutin and lv4 type decl *)
      let str = Data.type_to_string t in
      let idref = Ident.idref_of_string str in
      (if range_flag then (
        match t with
          | Data.Real -> " real [-10000.0;10000.0]"
          | Data.Int ->  " int [-10000;10000]"
          | Data.Enum(_, idl) -> " int [0;"^(string_of_int (List.length idl - 1)) ^"]" 
          | _ -> idref.Ident.id_id
       ) else idref.Ident.id_id)
    in
    let soc = try Soc.SocMap.find msk zesoc with Not_found -> assert false in
    let invars,outvars=soc.Soc.profile in
    let invars = SocExec.expand_profile false invars in
    let outvars = SocExec.expand_profile true outvars in
    let invars_str  = List.map (fun (n,t) -> n^":"^(my_type_to_string true t)) invars in
    let outvars_str = List.map (fun (n,t) -> n^":"^(my_type_to_string false t)) outvars in
    let name = main_node.Ident.id_id in
    let lutin_file_name = ("_"^name^"_env.lut") in
    let oc = open_out lutin_file_name in
    Lv6util.dump_entete oc; 
    output_string oc ("node " ^ (name) ^ "_env("^ (String.concat ";" outvars_str) ^ 
                         ") returns(" ^ (String.concat ";" invars_str) ^ ") =\n   loop true \n");
    flush oc; 
    close_out oc;
    output_string stdout (lutin_file_name ^" generated.\n"); 
    let oracle_file_name = ("_"^name^"_oracle.lus") in
    let oc = open_out oracle_file_name in
    let invars,outvars=soc.Soc.profile in
    let locals = List.map (fun (n,t) -> n^"_bis",t) outvars in
    let invars_str  = List.map (fun (n,t) -> n^":"^(my_type_to_string false t)) invars in
    let outvars_str = List.map (fun (n,t) -> n^":"^(my_type_to_string false t)) outvars in
    let prg = "node "^name^"_oracle("^(String.concat ";" (invars_str@outvars_str)) in
    let locals_str = List.map (fun (n,t) -> n^":"^(my_type_to_string false t)) locals in
    let ok = (* avoid name clash*)
      let all_vars = List.map fst (List.rev_append (List.rev_append locals invars) outvars) in
      let rec gen_ok str = if List.mem str all_vars then gen_ok ("_"^str) else str in
      gen_ok "ok" 
    in 
    let prg = prg ^") returns("^ok^":bool;"^(String.concat ";" locals_str)^");\nlet\n" in
    let locals_name  = List.map fst locals in
    let invars_name  = List.map fst invars in
    let prg = prg^"  ("^(String.concat "," locals_name)^") = "^name in
    let prg = prg^"("^(String.concat "," invars_name)^");\n  "^ok^" = (" in
    let (var_to_equals : Soc.var -> Soc.var -> string list) =
      fun (x,t) (y,_) -> 
        let rec aux x y t =
          match t with 
            | Data.Real ->  [x^"-"^y^" < 0.1 and "^ y^"-"^x^" < 0.1 "]
            | Data.Array(t,n) -> 
              let res = ref [] in
              for i = 0 to n-1 do
                let istr = string_of_int i in
                res := !res @ aux (x^"["^istr^"]") (y^"["^istr^"]") t
              done;
              !res
            | Data.Struct(n,fl) ->
              let do_field (fn,ft) = aux (x^"."^fn) (y^"."^fn) ft in
              List.flatten (List.map do_field fl)
            | _ ->  [x^"="^y]
        in
        aux x y t
    in
    let prg = try prg^(String.concat " and\n \t\t  " 
                       (List.flatten (List.map2 var_to_equals locals outvars)))^");\ntel;\n"
      with _ -> assert false
    in
    Lv6util.dump_entete oc; 
    output_string oc prg;
    flush oc; 
    close_out oc;
    output_string stdout (oracle_file_name ^" generated.\n"); 
    flush stdout


let main () = (
  (* Compile.init_appli () ; *)
  (* parse_args (); *)
  let opt = Lv6MainArgs.parse Sys.argv in
  Verbose.exe ~level:3 (fun () ->
    Gc.set { (Gc.get ()) with Gc.verbose = 0x01 }
  );
  if opt.run_unit_test then (
    UnifyType.unit_test ();
    exit 0
  );
  if (opt.infiles = []) then (
    Lv6MainArgs.usage stderr opt;
    exit 1
  );
  let new_dft_pack = Filename.basename (Filename.chop_extension (List.hd opt.infiles)) in
  Ident.set_dft_pack_name new_dft_pack;

  let main_node = 
    if opt.main_node = "" then None else 
      Some (Ident.idref_of_string opt.main_node)
  in
  if opt.outfile <> "" then opt.oc <- open_out opt.outfile;
  (try (
    let nsl = Compile.get_source_list opt opt.infiles in
    let lic_prg = Compile.doit opt nsl main_node in
    if opt.Lv6MainArgs.gen_autotest then 
      gen_autotest_files lic_prg main_node opt
    else if opt.exec then
      (match main_node with
        | None -> (
          let name = find_a_node opt in
          let nk = (Lic.node_key_of_idref (Ident.to_idref name)) in
          if LicPrg.node_exists lic_prg nk then (
            print_string ("WARNING: No main node is specified. I'll try with " ^ name ^"\n");
            flush stdout;
            let msk, zesoc = Lic2soc.f lic_prg nk in
            SocExec.f opt zesoc msk
          ) else (
            print_string ("Error: no node is specified, cannot exec.\n");
            flush stdout;
            exit 1
          )
        )
        | Some main_node -> 
          let msk, zesoc = Lic2soc.f lic_prg (Lic.node_key_of_idref main_node) in
          SocExec.f opt zesoc msk
      ) else (   
        LicPrg.to_file opt lic_prg main_node
       );

    Verbose.exe ~level:3 (fun () -> Gc.print_stat stdout);
   ) with 
      Sys_error(s) ->
        prerr_string (s^"\n");
        my_exit opt 1
    | Global_error s ->
      print_global_error s ;
      my_exit opt 1
    | Parse_error ->
      print_compile_error (Lxm.last_made ()) "syntax error";
      my_exit opt 1
    | Unknown_var(lxm,id) -> 
      print_compile_error lxm ("unknown variable (" ^ (Ident.to_string id) ^")");
      my_exit opt 1
    | Unknown_constant(lxm,str) -> 
      print_compile_error lxm ("unknown constant (" ^ str ^")");
      my_exit opt 1
    | Compile_error(lxm,msg) -> 
      print_compile_error lxm msg;
      my_exit opt 1
    | L2lCheckLoops.Error(lxm,msg,lic_prg) -> 
          (* Sometime it helps to see the current state of the faulty program *)
      LicPrg.to_file opt lic_prg main_node;
      flush  opt.oc;
      print_compile_error lxm msg;
      my_exit opt 1
    | Assert_failure (file, line, col)  -> 
      prerr_string (
        "\n*** oops: lus2lic internal error\n\tFile \""^ file ^
          "\", line " ^ (string_of_int line) ^ ", column " ^
          (string_of_int col) ^ "\n*** when compiling lustre program" ^
          (if List.length opt.infiles > 1 then "s " else " ") ^
          (String.concat ", " opt.infiles) ^ "\n"^
          "\n*** You migth want to sent a bug report to "^Lv6version.maintainer ^"\n") ; 
      my_exit opt 2
  );
       (* | Compile_node_error(nkey,lxm,msg) -> ( *)
       (* print_compile_node_error nkey lxm msg ; *)
       (* exit 1 *)
       (* ) *)
       (* | Global_node_error(nkey,msg) -> ( *)
       (* print_global_node_error nkey msg ; *)
       (* exit 1 *)
       (* ) *)

  close_out opt.oc
    
);;
main();