let split_subst (sub : t_subst) (u : term) (v : term)
  : term * term * t_split_subst * int list * int list =

  (* add_var adds n clever to (std_vs,xor_vs). If n has a type different from 0,
   * it will be added to std_vs, no matter what to_std is.
   * It will not produce duplicates in std_vs or xor_vs (insert is used).
   *
   * Input: n - an index of a variable
   *        to_std - true, if n should be added to std_vs
   *        to_xor - true, if n should be added to xor_vs
   *        std_vs - list of variables
   *        xor_vs - list of variables
   *)

  let add_var n (to_std,to_xor) (std_vs,xor_vs) =
    let to_std = to_std || (var_type n <> 0)
    in
    match (to_std,to_xor) with
    | (true ,true ) -> (insert n std_vs,insert n xor_vs)
    | (true ,false-> (insert n std_vs,         xor_vs)
    | (false,true ) -> (         std_vs,insert n xor_vs)
    | (false,false-> (         std_vs,         xor_vs)
  in

  (* Returns true, iff n is in std_vs or in xor_vs
   *)

  let mem_var n (std_vs,xor_vs) =
    List.mem n std_vs || List.mem n xor_vs
  in

  (* applies " to a list of terms " instead of just a single term
   *)

  let rec apply_f_to_list (in_std : bool) tas = function
    | t::ts ->
        let tas, t  = f in_std tas t in
        let tas, ts = apply_f_to_list in_std tas ts in
        (tas, t::ts)
    | [] -> tas, []
  (* applies " to a list of terms " instead of just a single term
   *)

  and apply_f_to_list_sign (in_std : bool) tas = function
    | (t,si)::ts ->
        let tas, t  = f in_std tas t in
        let tas, ts = apply_f_to_list_sign in_std tas ts in
        (tas, (t,si)::ts)
    | [] -> tas, []
  and
  (* f takes a term t and returns t2 which has the following properties:
   * - t2 contains only variables that are free or have a value different
   *   from a variable and an atom (except Atm(0) if t is std and Atm(n)
   *   (n<>0) if t is xor)
   * - otherwise t2 is the same as t
   *
   * In tas it collects all standard equations (in std), all xor
   * equations (in xor), all variables (in vars) and the remaining
   * substitution (in s).
   * f goes into the structure of t and collects all variables.
   * For every variable t it goes into the structure of the value of
   * this variable (if there is any). If there is a value t2 different
   * from Atm or Var it is either a standard or an xor expression and the
   * equation t = t2 will be added to std or xor, respectively.
   * The value for the variable will also be removed from s because the
   * information is now stored in std or xor and it is not necessary
   * to store it twice.
   *
   * The value of in_std is only relevant if t is a variable, otherwise it
   * is ignored. It indicates weather the appearance of t is in a std term
   * or an xor term.
   *)

  f (in_std : bool) (std,xor,vars,s as tas) (t : term) =
    match t with
    | Var(n) ->
        if mem_var n vars then
          (* n in vars means, that we have already handled it. We do not need to
           * do it twice, but if in_std is false we have to add it to xor_vs and
           * vice versa *)

          ((std,xor,(add_var n (in_std, not in_std) vars),s), t)
        else
          (* We found a variable that is not handled yet. If there is a value
           * for it in s, add this as an equation to std or xor, respectively.
           *
           * TODO: Here could be done some optimization, because most time we
           * look for the value of n in s and remove this from the substitution
           * afterwards. You could do this in one step. *)

          (match valeur n s with
          | Var(m) as t2 ->
              if m=n then
                (* m=n means, that n is free in s *)
                ((std,xor,add_var n (in_std, not in_std) vars, s), t)
              else
                (* forget that we saw Var(n) and replace it with Var(m) (= t2)
                 *)

                f in_std tas t2
          | Xor l ->
              (* add n to standard vars if in_std is true and add n to xor vars
               * in any case *)

              let vars = add_var n (in_std,true) vars in
              (* apply f to l *)
              let ((std, xor, vars, s), l) =
                apply_f_to_list false (std, xor, vars, remove_from_subs n s) l
              in
              (* add the equation 0 = t+l to xor *)
              ((std, (t::l)::xor, vars, s), t)
          | Atm(0) ->
              (* add n to standard vars if in_std is true and add n to xor vars
               * in any case *)

              let vars = add_var n (in_std,true) vars in
              (* do not replace an Atm(0), but add " to xor *)
              ((std, [t]::xor, vars, remove_from_subs n s), t)
          | Atm(_) as t2 ->
              if in_std then
                (* there is an atom t2 assigned to n so we can replace n by t2
                 * and forget about n *)

                ((std, xor, vars, s), t2)
              else
                (* There is an atom t2 assigned to n, but because we are in an
                 * xor term we cannot replace n by t2. *)

                (* Add n to std_vars and to xor_vars. *)
                let vars = add_var n (true,true) vars in
                (* Add n -> t2 to std. *)
                (((n,t2)::std, xor, vars, remove_from_subs n s), t)
          | u ->
              (* u has to be standard. *)
              (* Add n to xor vars if in_std is false and add n to standard vars
               * in any case. *)

              let vars = add_var n (true,not in_std) vars in
              (* Apply f on u *)
              let (std,xor,vars,s),u =
                f true (std, xor, vars, remove_from_subs n s) u in
              (* Add n -> u to std. *)
              (((n,u)::std, xor, vars, s), t)
          )
    | Atm(_) ->
        tas, t
    | Xor l ->
        let tas, l = apply_f_to_list false tas l in
        tas, (Xor l)
    | Uplet l ->
        let tas, l = apply_f_to_list true tas l in
        tas, (Uplet l)
    | PCrypt(m,k) ->
        let tas,m = f true tas m in
        let tas,k = f true tas k in
        tas, PCrypt(m,k)
    | SCrypt(m,k) ->
        let tas,m = f true tas m in
        let tas,k = f true tas k in
        tas, SCrypt(m,k)
    | PInv(t) ->
        let tas,t = f true tas t in
        tas, PInv(t)
    | Exp(t,l) ->
        let tas,t = f true tas t in
        let tas,l = apply_f_to_list_sign true tas l in
        tas, Exp(t,l)
  in
  (* in_std will be true, iff u and v are both std *)
  let in_std = match (u,v) with
               | (Atm(0),_     )
               | (Xor(_),_     )
               | (_     ,Atm(0))
               | (_     ,Xor(_)) -> false
               | _               -> true
  in
  (* Apply f on u with an empty tas. *)
  let tas,u = f in_std ([],[],([],[]),sub) u in
  (* Apply f on v with the tas obtained before. *)
  let (std_ls,xor_ls,(std_vars,xor_vars),(rem_ls,fv,ctr)),v = f in_std tas v in
  u,v,(std_ls,xor_ls,rem_ls,fv,ctr),std_vars,xor_vars