(** Time-stamp: <modified the 28/08/2008 (at 10:27) by Erwan Jahier> *)

open Lxm
open SyntaxTree
open SyntaxTreeCore
open Errors
open SyntaxTabUtils

let (doit:
       (Ident.t, SyntaxTree.model_info Lxm.srcflagged) Hashtbl.t ->
      (SyntaxTree.pack_info  Lxm.srcflagged) ->
      SyntaxTree.pack_given) =
  fun mtab pdata -> (
    match (pdata.it.pa_def) with
        PackGiven pg -> pg
          (* on garde tel-quel ... *)
          
      | PackInstance pi -> (
          (* recherche du mod�le *)
          let mi = try Hashtbl.find mtab pi.pi_model 
          with Not_found ->
            let msg = Printf.sprintf "bad pack instance: model %s undeclared"
              (Ident.to_string pi.pi_model)
            in
              raise ( Compile_error (pdata.src, msg))
          in
            (*-----------INIT-----------------------------------*)
            (* On part du packbody du mod�le, dont on duplique les tables :*)
          let ctab = Hashtbl.copy mi.it.mo_body.pk_const_table in
          let ttab = Hashtbl.copy mi.it.mo_body.pk_type_table in
          let otab = Hashtbl.copy mi.it.mo_body.pk_node_table in
            (* liste des nouveaux define ... *)
          let newdefs = ref [] in
            (* liste des nouveaux provides ... *)
          let newprov = ref [] in
            (* On met en correspondance les pi_args avec les mo_needs *)
          let args = pi.pi_args in
          let pars = mi.it.mo_needs in
            (*--------------------------------------------------*)

          (* la fonction qui traite un couple ... *)
          let (check_arg : static_param srcflagged -> static_arg srcflagged -> unit) =
            fun param arg -> 
              (* message d'erreur standard *)
              let instance_error () = 
                let msg = Printf.sprintf
                  "bad argument in package instance: %s" (Lxm.details param.src)
                in
                  raise (Compile_error (arg.src, msg)) 
              in
                (* on a soit un ident, � checker plus tard, soit une
                   expression de la bonne nature *)
                match (param.it) with
                  | StaticParamType s -> (
                      let te = match (arg.it) with
                          StaticArgIdent idr -> 
                            Lxm.flagit (Named_type_exp idr) arg.src
                        | StaticArgType x -> x
                        | _ -> instance_error ()
                      in
                      let ti = AliasedType (s, te) in
                      let x = Lxm.flagit (TypeInfo ti) param.src in
                        newprov := x::!newprov ;
                        let y = Lxm.flagit ti param.src in
                          put_in_tab "type" ttab s y ;
                          newdefs := (TypeItem s)::!newdefs
                    ) 
                  | StaticParamConst (s,te) -> (
                      let ce = match (arg.it) with
                        | StaticArgIdent idr -> 
                            ParserUtils.leafexp arg.src (IDENT_n idr) 
                        | StaticArgConst x -> x
                        | _ -> instance_error ()
                      in
                      let ci = DefinedConst (s, Some te, ce) in
                      let x = Lxm.flagit (ConstInfo ci) param.src in
                        newprov := x::!newprov ;
                        let y = Lxm.flagit ci param.src in
                          put_in_tab "const" ctab s y ;
                          newdefs := (ConstItem s)::!newdefs
                    ) 
                  | StaticParamNode (s, inl, outl, has_memory) -> (
                      let by_pos_op = match (arg.it) with
                        | StaticArgIdent idr -> 
                            CALL_n(Lxm.flagit ((idr,[])) arg.src)
                        | StaticArgNode by_pos_op -> by_pos_op 
                        | _ -> instance_error () 
                      in
                      let sparams = [] in
                      let ni = {
                        name = s;
                        static_params = sparams;
                        vars = Some (ParserUtils.build_node_var inl outl None);
                        def = Alias (flagit by_pos_op arg.src);
                        has_mem = has_memory;
                        is_safe = true;
                      } 
                      in
                      let x = Lxm.flagit (NodeInfo ni) param.src in
                        newprov := x::!newprov ;
                        let y = Lxm.flagit ni param.src in
                          put_in_tab "node" otab s y ;
                          newdefs := (NodeItem (s,sparams))::!newdefs
                    ) 
                      (* check_arg *)
          in
          let (sargs_pack : Ident.pack_name srcflagged list) =
            List.fold_left
              (fun acc arg -> 
                (match arg.it with
                   | StaticArgIdent(idref) ->
                       (match Ident.pack_of_idref idref with
                          | None -> acc
                          | Some p -> 
                              let p_flagged = Lxm.flagit p arg.src in
                                if List.mem p_flagged acc then acc else p_flagged::acc
                       )
                   | _ -> acc
                )
              )
              []
              args
          in
          let pars_nb = string_of_int (List.length pars)
          and args_nb = string_of_int (List.length args) in
            try (
              (*------------TRAITEMENT---------------------------------*)
              if (pars_nb <> args_nb) then
                raise(Compile_error 
                        (pdata.src, 
                         ("\n*** " ^pars_nb ^ 
                            " arguments are expected, but "^args_nb^
                            " were provided when defining package "^
                            (Ident.pack_name_to_string pdata.it.pa_name)
                         )));
              List.iter2 check_arg pars args;
              (* on fabrique un pack_given valide avec les infos r�colt�es *)   
              let body = {
                pk_const_table = ctab ;
                pk_type_table = ttab ;
                pk_node_table = otab ;
                pk_def_list = List.append
                  (mi.it.mo_body.pk_def_list) 
                  (List.rev !newdefs)
              } in
                (* les provides du mod�le + les nouveaux de newprov *)
                (* SAUF SI ON EXPORTE DEJA TOUT !                   *)
              let prov = match (mi.it.mo_provides) with
                  Some l -> (
                    Some (List.append l (List.rev !newprov))
                  ) 
                |  None -> None
              in
              let pg = {
                (* les uses du mod�le + les packages utilis�s par les arg statiques *)
                pg_uses = mi.it.mo_uses @ sargs_pack;
                pg_provides = prov ;
                pg_body = body ;
              } in
                pg
            ) with Invalid_argument _ -> (
              let msg = Printf.sprintf
                "bad pack instance: %d args provided while model %s has %d params"
                (List.length args)
                (Ident.to_string pi.pi_model)
                (List.length pars)
              in
                raise ( Compile_error (pdata.src, msg))
            )
        )
  )