(* Time-stamp: <modified the 08/04/2013 (at 10:43) by Erwan Jahier> *)

(** Synchronous Object Code for Predefined operators. *)

let finish_me lxm 
    = print_string ("\nsocPref.ml:"^(Lxm.details lxm)^" ->  finish me!\n")


open Soc

(* Some aliases *)
let b = Soc.Bool
let i = Soc.Int
let r = Soc.Real

let aa t1 t2 =  ["x", t1], ["z", t2]
let aaa t1 t2 t3 =  ["x", t1; "y",t2], ["z", t3]
let aab t1 t2 =  ["x", t1; "y",t2], ["z", Bool]

(* if/then/else *)
let baaa t = ["c", b; "xt", t; "xe", t], ["z", t]

let (soc_profile_of_types : Soc.var_type list -> var list * var list) = 
  function
    | [t1; t2] -> aa t1 t2
    | [t1;t2;t3] -> aaa t1 t2 t3
    | [Bool;t1;t2;t3] -> baaa t1 
    | tl  -> 
      print_string ("Unsupported case: "^ (
        String.concat "," (List.map SocUtils.string_of_type_ref tl)));
      flush stdout;
      assert false

(* For diese and nor *)  
let (soc_profile_of_types_nary : Soc.var_type list -> var list * var list) = 
  fun vl -> 
    ["x", Array(Bool,List.length vl)], ["z",Bool]
        

let step11 = { (* a useful alias again *)
  name    = "step";
  lxm     = Lxm.dummy "predef soc";
  idx_ins  = [0];
  idx_outs = [0];
  impl    = Predef;
}
let step21 impl = { (* a useful alias again *)
  name    = "step";
  lxm     = Lxm.dummy "predef soc";
  idx_ins  = [0;1];
  idx_outs = [0];
  impl    = Predef;
}

(* used to build predef soc with no memory *)
let make_soc key profile steps =  {
      key      = key;
      profile  = profile;
      instances = [];
(*       init     = None; *)
      precedences  = [];
      step     = steps;
      have_mem = None;
    }


let first_instant = Var("first_instant", Bool)
let (get_mem_name : Soc.key -> var_type -> string) =
  fun (k,tl,_) vt -> 
    match Str.split (Str.regexp "::") k with        
      | ["Lustre";op] -> (
        match op.[0] with
          | 'i' | 'b' | 'r' -> String.set op 0 '_'; ("mem"^op)
          | _ ->  "mem_"^op
      )
      | _ ->  "mem_"^k


(* exported *)
let of_soc_key : Soc.key -> Soc.t = 
  fun sk -> 
    let (id, tl, _) = sk in
    let sp = soc_profile_of_types in
    let sp_nary = soc_profile_of_types_nary in
    match id with
      | "Lustre::mod" -> (make_soc sk (sp tl) [step11])
      | "Lustre::uminus" -> (make_soc sk (sp tl) [step11])
      | "Lustre::not" -> (make_soc sk (sp tl) [step11])
      | "Lustre::real2int" -> (make_soc sk (sp tl) [step11])
      | "Lustre::int2real" -> (make_soc sk (sp tl) [step11])

      | "Lustre::plus"  -> (make_soc sk (sp tl) [step21 None])      
      | "Lustre::times" -> (make_soc sk (sp tl) [step21 None])
      | "Lustre::div"   -> (make_soc sk (sp tl) [step21 None])
      | "Lustre::minus" -> (make_soc sk (sp tl) [step21 None])

      | "Lustre::lt"  -> (make_soc sk (sp tl) [step21 None])
      | "Lustre::gt"  -> (make_soc sk (sp tl) [step21 None])
      | "Lustre::lte" -> (make_soc sk (sp tl) [step21 None])
      | "Lustre::gte" -> (make_soc sk (sp tl) [step21 None])

      | "Lustre::and" -> (make_soc sk (sp tl) [step21 None])
      | "Lustre::eq" -> (make_soc sk (sp tl) [step21 None])
      | "Lustre::neq" -> (make_soc sk (sp tl) [step21 None])
      | "Lustre::or"  -> (make_soc sk (sp tl) [step21 None])
      | "Lustre::xor" -> (make_soc sk (sp tl) [step21 None])
      | "Lustre::impl" -> (make_soc sk (sp tl) [step21 None])
        
      (* Those have instances *)
      | "Lustre::current" -> (
        let _,tl,_ = sk in
        let t = List.hd tl in
        let pre_mem:var = (get_mem_name sk t, t) in
        let prof = sp tl in
        let v1,vout = match prof with ([v1],[vout]) -> v1,vout | _ -> assert false in
        {
          key      = sk;
          profile  = (sp tl);
          instances = [];
          have_mem = Some (t, None); (* so that pre_mem exist *)
          step  = [
            {
              name    = "get";
              lxm     = Lxm.dummy "predef soc";
              idx_ins  = [];
              idx_outs = [0];
              impl    = Gaol([pre_mem],[Call([Var(vout)], Assign, [Var(pre_mem)])]);
            };
            {
              name    = "set";  
              lxm     = Lxm.dummy "predef soc";
              idx_ins  = [0];
              idx_outs = [];
              impl    = Gaol([pre_mem],[Call([Var(pre_mem)], Assign, [Var(v1)])]);
            };
          ];
          precedences = ["get", ["set"]];
        }
      )  
      | "Lustre::pre" ->  
        let _,tl,_ = sk in
        let t = List.hd tl in
        let pre_mem:var = (get_mem_name sk t, t) in
        let prof = sp tl in
        let v1,vout = match prof with ([v1],[vout]) -> v1,vout | _ -> assert false in
        {
          key      = sk;
          profile  = (sp tl);
          instances = [];
          have_mem = Some (t, None); (* so that pre_mem exist *)
          step  = [
            {
              name    = "get";
              lxm     = Lxm.dummy "predef soc";
              idx_ins  = [];
              idx_outs = [0];
              impl    = Gaol([pre_mem],[Call([Var(vout)], Assign, [Var(pre_mem)])]);
            };
            {
              name    = "set";  
              lxm     = Lxm.dummy "predef soc";
              idx_ins  = [0];
              idx_outs = [];
              impl    = Gaol([pre_mem],[Call([Var(pre_mem)], Assign, [Var(v1)])]);
            };
          ];
          precedences = ["set", ["get"]];
        }
      | "Lustre::arrow"  ->  
        let prof = sp tl in
        {
          key      = sk;
          profile  = prof;
          instances = [];
          step  = [
            {
              name    = "step";
              lxm     = Lxm.dummy "predef soc";
              idx_ins  = [0;1];
              idx_outs = [0];
              impl    = Predef;
            }
          ];
          precedences   = [];
          have_mem = None;
        }
      | "Lustre::fby"  -> assert false
      | "Lustre::if"  ->  {
        key      = sk;
        profile  = (sp tl);
        instances = [];
        (*         init     = None; *)
        precedences  = [];
        have_mem = None;
        step     = [
          {
            name    = "step";
            lxm     = Lxm.dummy "predef soc";
            idx_ins  = [0; 1; 2];
            idx_outs = [0];
            impl    = Predef;
          }        ];
      } 
      | "Lustre::nor"  -> 
        let size = match sk with
          | _,[Array(Bool,size);_],_  -> size
          | _ -> assert false
        in
        {
          Soc.key       = sk;
          Soc.profile   = sp_nary tl;
          Soc.instances = [] ;
          Soc.step      = [
            {
              name     = "step";
              lxm      = Lxm.dummy "nor soc";
              idx_ins  = [0];
              idx_outs = [0];
              impl     = Boolred(0,0, size);
            }
          ];
          Soc.have_mem    = None;
          Soc.precedences = [];
        } 
      | "Lustre::diese"  ->
        let size = match sk with
          | _,[Array(Bool,size);_],_  -> size
          | _ -> assert false
        in
        {
          Soc.key       = sk;
          Soc.profile   = sp_nary tl;
          Soc.instances = [] ;
          Soc.step      = [
            {
              name     = "step";
              lxm      = Lxm.dummy "diese soc";
              idx_ins  = [0];
              idx_outs = [0];
              impl     = Boolred(1,1, size);
            }
          ];
          Soc.have_mem    = None;
          Soc.precedences = [];
        } 
      | _ -> 
        print_string ("*** The soc of "^id ^ " is not defined. FINISH ME! \n"); flush stdout;
        assert false

    


(** Instancie un composant polymorphe avec un type concret. *)
let instanciate_soc: Soc.t -> Soc.var_type -> Soc.t = 
  fun c concrete_type ->
    let rec instanciate_type vt =
      match vt with 
        | Alpha _ ->  concrete_type 
        | Struct(sn, fl) -> 
          Struct(sn, List.map (fun (id,vt) -> (id,instanciate_type vt)) fl)
        | Array(vt,i) -> Array(instanciate_type vt,i)
        | vt -> vt
    in
    let new_profile =
      List.map (fun (vn,vt) -> vn, instanciate_type vt) (fst c.profile),
      List.map (fun (vn,vt) -> vn, instanciate_type vt) (snd c.profile)
    in
    let instanciate_key  (key1, key2, key3) = 
      (key1, List.map instanciate_type key2, key3) 
    in
    let new_key = instanciate_key c.key in 
    let new_instances = 
      List.map (fun (id,sk) -> (id,instanciate_key sk)) c.instances
    in
      { 
        c with 
          key = new_key;
          profile = new_profile;
          instances = new_instances;
      }


(*
  XXX Faut-il definir une version g�n�rique des composants tranches ? 

  Je les ai d�fini directement via "make_slice_soc", ce qui
  n'est pas homogene avec la facon dont sont trait�s les autres
  composants g�n�riques style 'fby'.

  Le truc, c'est que je ne sais pas trop quoi mettre dans la version
  g�n�rique, et comme celle-ci est destin�e � �tre instanci�e... En
  effet, le type de sortie des composants tranche depend de la
  slice_info pass� en parametre lors de l'instanciation des composant
  g�n�riques. Je pourrais mettre un type alpha, mais je trouve ca
  idiot, alors je ne le fais pas...

  Une autre solution pour rendre ce traitement homogene serait de ne
  pas passer par une version g�n�rique pour les composants fby et
  consort. A voir.

  idem pour "x^n" (Hat_n).
*)

let make_slice_soc: Lic.slice_info -> Soc.var_type -> Soc.t = 
  fun si t -> 
    let (f,l,step) = (si.Lic.se_first, si.Lic.se_last,si.Lic.se_step) in
    let sub_array_type = 
      match t with
        | Soc.Array(t_elt,size) -> 
            let slice_size = 1+abs( (l - f) / step) in
              Soc.Array(t_elt, slice_size)
        | _ -> assert false
    in
      {
        key = ("array_slice", [t], Some (f, l, step));
        profile  = (["t", t], ["st", sub_array_type ]);
        instances = [];
        step  = [
          {
            name    = "step";
            lxm     = Lxm.dummy "predef soc";
            idx_ins  = [0];
            idx_outs = [0];
            impl    = Predef;
          };
        ];
        precedences   = [];
        have_mem = None;
(*         init      = None; *)
      } 


let (make_merge_soc: Soc.key -> Soc.t) = 
  fun sk -> 
    let (id, tl, _) = sk in
    let in_tl, out_t = match List.rev tl with x::l -> List.rev l, x | [] -> assert false in
    let profile_in = ("clk", List.hd in_tl)::
      (List.mapi (fun i vt -> "x"^(string_of_int i), vt) in_tl)
    in
    let i = List.length in_tl in
    {
      Soc.key       = sk;
      Soc.profile   = profile_in, ["z", out_t];
      Soc.instances = [] ;
      Soc.step      = [
        {
          name     = "step";
          lxm      = Lxm.dummy "merge soc";
          idx_ins  = SocUtils.gen_index_list (i+1);
          idx_outs = [0];
          impl     = Predef;
        }
      ];
      Soc.have_mem    = None;
      Soc.precedences = [];
    } 

let make_array_soc: int -> Soc.var_type -> Soc.t = 
  fun i t -> 
    let iprof = 
      let res = ref [] in
      for k=i downto 1 do
        res:= ("x"^(string_of_int k),t) :: !res;
      done;
      !res
    in
    let array_type = Array(t,i) in
    let key_prof = (List.map snd iprof) @ [array_type] in
      {
        key = ("Lustre::array", key_prof, None);
        profile  = (iprof, ["z", array_type]);
        instances = [];
        step  = [
          {
            name    = "step";
            lxm     = Lxm.dummy "predef array soc";
            idx_ins  = SocUtils.gen_index_list i;
            idx_outs = [0];
            impl    = Predef;
          };
        ];
        precedences   = [];
        have_mem = None;
      } 

let make_array_concat_soc: int -> int -> Soc.var_type -> Soc.t = 
  fun s1 s2 t -> 
    let iprof = (["x", Array(t,s1); "y",  Array(t,s2)], ["z", Array(t,s1+s2)])in
    let key_prof = [Array(t,s1); Array(t,s2); Array(t,s1+s2)] in
      {
        key = ("Lustre::concat", key_prof, None);
        profile  = iprof;
        instances = [];
        step  = [
          {
            name    = "step";
            lxm     = Lxm.dummy "predef array concat soc";
            idx_ins  = [0;1];
            idx_outs = [0];
            impl    = Predef;
          };
        ];
        precedences   = [];
        have_mem = None;
      } 


let make_hat_soc: int -> Soc.var_type -> Soc.t = 
  fun i t -> 
    let array_type = 
      match t with
        | Soc.Alpha _ -> assert false
        | t -> Soc.Array(t,i)
    in
      {
        key = ("Lustre::hat", [t;array_type], None);
        profile  = ([("x", t)], ["z", array_type]);
        instances = [];
        step  = [
          {
            name    = "step";
            lxm     = Lxm.dummy "predef hat soc";
            idx_ins  = [0];
            idx_outs = [0];
            impl    = Predef;
          };
        ];
        precedences   = [];
        have_mem = None;
      } 



let output_type_of_op op tl =
  match op with
    | "Lustre::eq"
    | "Lustre::neq"
    | "Lustre::lt"
    | "Lustre::gt"
    | "Lustre::lte"
    | "Lustre::gte"  
    | "Lustre::nor"
    | "Lustre::diese"
      -> Bool
    | "Lustre::real2int" -> Int
    | "Lustre::int2real" -> Real
    | "Lustre::if" -> List.hd (List.tl tl)
    | _ -> List.hd tl


let (soc_interface_of_pos_op: 
       Lxm.t -> Lic.by_pos_op -> Soc.var_type list -> Soc.t) =
  fun lxm op types ->
    match (op, types) with
      | Lic.PREDEF_CALL ({Lxm.it=("Lustre","if"),[]}), _   -> 
        let concrete_type = List.nth types 1 in 
        let soc = of_soc_key ("Lustre::if", types@[concrete_type], None) in
        instanciate_soc soc concrete_type
      | Lic.PREDEF_CALL {Lxm.it=(op,sargs)}, _ -> 
        assert (sargs=[]);
        let soc_name = Ident.string_of_long op in
        let out_type = output_type_of_op soc_name types in
        let soc = of_soc_key (soc_name, types@[out_type], None) in
        soc 
      | Lic.FBY, _ -> 
        let concrete_type = List.nth types 0 in 
        let soc = of_soc_key (("Lustre::fby"), types@[concrete_type], None) in
        instanciate_soc soc concrete_type
      | Lic.PRE, _ ->
        let concrete_type = List.nth types 0 in 
        let soc = of_soc_key (("Lustre::pre"), types@[concrete_type], None) in
        instanciate_soc soc concrete_type
      | Lic.CURRENT, _ ->
        let concrete_type = List.nth types 0 in 
        let soc = of_soc_key (("Lustre::current"), types@[concrete_type], None) in
        instanciate_soc soc concrete_type
      | Lic.ARROW, _ ->
        let concrete_type = List.nth types 0 in 
        let soc = of_soc_key (("Lustre::arrow"), types@[concrete_type], None) in
        let soc = instanciate_soc soc concrete_type in
        soc
      | Lic.HAT i,_ -> 
        let elt_type = List.nth types 0 in
        (make_hat_soc i elt_type)

      | Lic.ARRAY, _ ->  
        let elt_type = List.nth types 0 in
        let i = (List.length types) in 
        (make_array_soc i elt_type)

      | Lic.CONCAT ,  [Array (t1, s1); Array (t2, s2)]->  
        assert (t1=t2);
        (make_array_concat_soc s1 s2 t1)
      | Lic.CONCAT ,  _ ->  assert false

      | Lic.CALL _,_ ->  assert false (* XXX todo *)
      | Lic.CONST _ ,  _ ->  assert false


      (* Those are not soc *)
      | Lic.ARRAY_SLICE sinfo,_  -> assert false

      | Lic.VAR_REF _, _ -> assert false
      | Lic.CONST_REF _, _ -> assert false
      | Lic.STRUCT_ACCESS _, _ -> assert false
      | Lic.WHEN _, _ -> assert false
      | Lic.TUPLE, _ -> assert false
      | Lic.ARRAY_ACCES _, _ -> assert false
        

(* 
21/02/2013 : ai-je vraiment besoin de ca maintenant que les metaop ont �t� encapsul�
   dans des noeuds ? bon, je garde quelque temps en commentaire au cas ou...
      | Lic.Fill(node,size), _ 
      | Lic.FillRed(node,size), _ 
      | Lic.Red(node,size), _ -> 
        let soc = _key node with
          | Undef name -> Undef name
             (* Given 
                - a node n of type 
                tau * tau_1 * ... * tau_n -> tau * teta_1 * ... * teta_l
                - a integer c
                
                the red expression has the profile: 
                tau * tau_1^c * ... * tau_n^c  -> tau * teta_1^c * ... * teta_l^c
             *)
          | Some  c -> 
            let arrayse l = 
              let exp (id,t) = 
                match t with 
                  | Soc.Alpha _  -> assert false 
                  | t -> id, Soc.Array(t,size)
              in
              match l with 
                | [] -> assert false 
                | (id,t)::tail ->(id, t):: (List.map exp tail)
            in
            Some  { 
                     (* XXX la clef devrait contenir le node et la taille ? 
		                  
		                  Les socosants iterateurs ne meritent ils pas un traitement
		                  specifique ?
		                  Ce que je veux, c'est 
		                  - y mettre toute l'information necessaire pour pouvoir generer 
		                  la boucle for qui va bien,
		                  - garder une forme synthetique qui permette de faire des 
		                  analyses et de la v�rification

	                  *)
              key = ("fillred" ^ node ^ (string_of_int size), [], None);
              profile  = 
                (arrayse (fst c.profile), arrayse (snd c.profile));
              instances = c.instances;
              step  = c.step; 
                     (* XXX non ! le probleme, c'est que cette methode step 
		                  doit �tre fabriqu�e � partir de la methode step 
		                  du noeud it�r�, et que je n'ai rien pour exprimer 
		                  ce genre de truc pour l'instant.
	                  *)
              precedences  = [];
              init = c.init; (* XXX non ! *)
            } 
        )
      | Lic.Map(node,size), _ -> 
        let soc  = _key node with
          | Undef name -> Undef name
          | Some  c -> 
                 (* Given 
                    - a node n of type: tau_1 * ... * tau_n -> teta_1 * ... * teta_l
                    - an integer c
                    
                    The profile of map is: 
                    tau_1^c * ... * tau_n^c -> teta_1^c * ... * teta_l^c
                 *)
            let arrayse l = 
              let exp (id,t) = 
                match t with 
                  | Soc.Alpha _  -> assert false 
                  | t -> id, Soc.Array(t,size)
              in
              (List.map exp l)
            in
            Some  { 
              key = ("map" ^ node ^ (string_of_int size), [], None);
              profile  = 
                (arrayse (fst c.profile), arrayse (snd c.profile));
              instances = c.instances;
              step  = c.step; (* XXX non ! *)
              precedences   = [];
              init      = c.init;
            } 
        )
      | Lic.BoolRed(i,j,k), _  -> Errors.finish_me lxm ; assert false

      (* Cas particulier du boolred *)
      | Lic.DIESE, _-> Errors.finish_me lxm ; assert false
      | Lic.NOR ,_-> Errors.finish_me lxm ; assert false


 *)