(* $Id$ *)
 
+let exp_implicit metasenv subst context expty =
+ function      
+  | Some `Closed -> NCicMetaSubst.mk_meta metasenv [] expty
+  | Some `Type | None -> NCicMetaSubst.mk_meta metasenv context expty
+  | _ -> assert false
+;;
+
+let sort_of_prod localise metasenv subst context (name,s) t (t1, t2) =
+   let restrict metasenv subst = function
+     | NCic.Meta (i,(_,(NCic.Irl 0 | NCic.Ctx []))) as t -> 
+        metasenv, subst, t
+     | NCic.Meta (i,(_,lc)) as t ->
+        let len = match lc with NCic.Irl len->len | NCic.Ctx l->List.lenght l in
+        let metasenv, subst, _ = 
+          NCicMetaSubst.restrict metasenv subst i (HExtlib.seq 1 len) 
+        in
+        metasenv, subst, t
+     | t -> metasenv, subst, t 
+   in
+   let t1 = R.whd ~subst context t1 in
+   let t2 = R.whd ~subst ((name,C.Decl s)::context) t2 in
+   let metasenv, subst, t1 = restrict metasenv subst t1 in
+   let metasenv, subst, t2 = restrict metasenv subst t2 in
+   match t1, t2 with
+   | C.Sort _, C.Sort C.Prop -> metasenv, subst, t2
+   | C.Sort (C.Type u1), C.Sort (C.Type u2) ->
+        metasenv, subst, C.Sort (C.Type (u1@u2)) 
+   | C.Sort C.Prop,C.Sort (C.Type _) -> metasenv, subst, t2
+   | C.Meta _, C.Sort _
+   | C.Meta _, C.Meta _
+   | C.Sort _, C.Meta  _ -> metasenv, subst, t2
+   | x, _ | _, x -> 
+      let y, context = 
+        if x == t1 then s, context else t, ((name,C.Decl s)::context)
+      in
+      raise (TypeCheckerFailure (lazy (Printf.sprintf
+        "%s is expected to be a type, but its type is %s that is not a sort" 
+         (PP.ppterm ~subst ~metasenv ~context y) 
+         (PP.ppterm ~subst ~metasenv ~context x))))
+;;
+
+let rec typeof 
+  ?(localize=fun _ -> Stdpp.dummy) metasenv subst context term expty 
+=
+  let force_ty metasenv subst context t infty = function
+    | Some expty ->
+       (match t with
+       | NCic.Implicit _ -> metasenv, subst, t, expty
+       | _ ->
+         let metasenv, subst = 
+           NCicUnification.unify metasenv subst context infty expty in
+         metasenv, subst, t, expty)
+    | None -> metasenv, subst, t, infty
+  in
+  let rec typeof_aux metasenv subst context expty = 
+    fun t -> (*prerr_endline (PP.ppterm ~metasenv ~subst ~context t);*)
+    let metasenv, subst, t, infty = 
+    match t with
+    | C.Rel n ->
+        let infty = 
+         (try
+           match List.nth context (n - 1) with
+           | (_,C.Decl ty) -> S.lift n ty
+           | (_,C.Def (_,ty)) -> S.lift n ty
+         with Failure _ -> 
+           raise (RefineFailure (lazy (localize t,"unbound variable"))))
+        in
+        metasenv, subst, t, infty
+    | C.Sort (C.Type [false,u]) -> metasenv,subst,t,(C.Sort (C.Type [true, u]))
+    | C.Sort (C.Type _) -> 
+        raise (AssertFailure (lazy ("Cannot type an inferred type: "^
+          NCicPp.ppterm ~subst ~metasenv ~context t)))
+    | C.Sort _ -> metasenv,subst,t,(C.Sort (C.Type NCicEnvironment.type0))
+    | C.Implicit infos -> 
+         let metasenv,t,ty = exp_implicit metasenv subst context infos expty in
+         metasenv, subst, t, ty 
+    | C.Meta (n,l) as t -> 
+       let ty =
+        try
+         let _,_,_,ty = U.lookup_subst n subst in 
+        with U.Subst_not_found _ -> try
+         let _,_,ty = U.lookup_meta n metasenv in 
+         match ty with C.Implicit _ -> assert false | _ -> c,ty 
+        with U.Meta_not_found _ ->
+         raise (AssertFailure (lazy (Printf.sprintf
+          "%s not found" (PP.ppterm ~subst ~metasenv ~context t))))
+       in
+       metasenv, subst, t, S.subst_meta l ty
+    | C.Const _ -> 
+       metasenv, subst, t, NCicTypeChecker.typeof ~subst ~metasenv context t
+    | C.Prod (name,s,t) as orig ->
+       let metasenv, subst, s, s1 = typeof_aux metasenv subst context None s in
+       let context = (name,(NCic.Decl s))::context in
+       let metasenv, subst, t, s2 = typeof_aux metasenv subst context None t in
+       let metasenv, subst, ty = 
+         sort_of_prod localize metasenv subst context (name,s) t (s1,s2)
+       in
+       metasenv, subst, orig, ty
+    | C.Lambda (n,s,t) ->
+       let sort = typeof_aux context s in
+       (match R.whd ~subst context sort with
+       | C.Meta _ | C.Sort _ -> ()
+       | _ ->
+         raise
+           (TypeCheckerFailure (lazy (Printf.sprintf
+             ("Not well-typed lambda-abstraction: " ^^
+             "the source %s should be a type; instead it is a term " ^^ 
+             "of type %s") (PP.ppterm ~subst ~metasenv ~context s)
+             (PP.ppterm ~subst ~metasenv ~context sort)))));
+       let ty = typeof_aux ((n,(C.Decl s))::context) t in
+         C.Prod (n,s,ty)
+    | C.LetIn (n,ty,t,bo) ->
+       let ty_t = typeof_aux context t in
+       let _ = typeof_aux context ty in
+       if not (R.are_convertible ~subst context ty_t ty) then
+         raise 
+          (TypeCheckerFailure 
+            (lazy (Printf.sprintf
+              "The type of %s is %s but it is expected to be %s" 
+                (PP.ppterm ~subst ~metasenv ~context t) 
+                (PP.ppterm ~subst ~metasenv ~context ty_t) 
+                (PP.ppterm ~subst ~metasenv ~context ty))))
+       else
+         let ty_bo = typeof_aux  ((n,C.Def (t,ty))::context) bo in
+         S.subst ~avoid_beta_redexes:true t ty_bo
+    | C.Appl (he::(_::_ as args)) ->
+       let ty_he = typeof_aux context he in
+       let args_with_ty = List.map (fun t -> t, typeof_aux context t) args in
+       eat_prods ~subst ~metasenv context he ty_he args_with_ty
+   | C.Appl _ -> raise (AssertFailure (lazy "Appl of length < 2"))
+   | C.Match (Ref.Ref (_,Ref.Ind (_,tyno,_)) as r,outtype,term,pl) ->
+      let outsort = typeof_aux context outtype in
+      let _,leftno,itl,_,_ = E.get_checked_indtys r in
+      let constructorsno =
+        let _,_,_,cl = List.nth itl tyno in List.length cl
+      in
+      let parameters, arguments =
+        let ty = R.whd ~subst context (typeof_aux context term) in
+        let r',tl =
+         match ty with
+            C.Const (Ref.Ref (_,Ref.Ind _) as r') -> r',[]
+          | C.Appl (C.Const (Ref.Ref (_,Ref.Ind _) as r') :: tl) -> r',tl
+          | _ ->
+             raise 
+               (TypeCheckerFailure (lazy (Printf.sprintf
+                 "Case analysis: analysed term %s is not an inductive one"
+                 (PP.ppterm ~subst ~metasenv ~context term)))) in
+        if not (Ref.eq r r') then
+         raise
+          (TypeCheckerFailure (lazy (Printf.sprintf
+            ("Case analysys: analysed term type is %s, but is expected " ^^
+             "to be (an application of) %s")
+            (PP.ppterm ~subst ~metasenv ~context ty) 
+            (PP.ppterm ~subst ~metasenv ~context (C.Const r')))))
+        else
+         try HExtlib.split_nth leftno tl
+         with
+          Failure _ ->
+           raise (TypeCheckerFailure (lazy (Printf.sprintf 
+           "%s is partially applied" 
+           (PP.ppterm  ~subst ~metasenv ~context ty)))) in
+      (* let's control if the sort elimination is allowed: [(I q1 ... qr)|B] *)
+      let sort_of_ind_type =
+        if parameters = [] then C.Const r
+        else C.Appl ((C.Const r)::parameters) in
+      let type_of_sort_of_ind_ty = typeof_aux context sort_of_ind_type in
+      check_allowed_sort_elimination ~subst ~metasenv r context
+       sort_of_ind_type type_of_sort_of_ind_ty outsort;
+      (* let's check if the type of branches are right *)
+      if List.length pl <> constructorsno then
+       raise (TypeCheckerFailure (lazy ("Wrong number of cases in a match")));
+      let j,branches_ok,p_ty, exp_p_ty =
+        List.fold_left
+          (fun (j,b,old_p_ty,old_exp_p_ty) p ->
+            if b then
+              let cons = 
+                let cons = Ref.mk_constructor j r in
+                if parameters = [] then C.Const cons
+                else C.Appl (C.Const cons::parameters)
+              in
+              let ty_p = typeof_aux context p in
+              let ty_cons = typeof_aux context cons in
+              let ty_branch = 
+                type_of_branch ~subst context leftno outtype cons ty_cons 0 
+              in
+              j+1, R.are_convertible ~subst context ty_p ty_branch,
+              ty_p, ty_branch
+            else
+              j,false,old_p_ty,old_exp_p_ty
+          ) (1,true,C.Sort C.Prop,C.Sort C.Prop) pl
+      in
+      if not branches_ok then
+        raise
+         (TypeCheckerFailure 
+          (lazy (Printf.sprintf ("Branch for constructor %s :=\n%s\n"^^
+          "has type %s\nnot convertible with %s") 
+          (PP.ppterm  ~subst ~metasenv ~context
+            (C.Const (Ref.mk_constructor (j-1) r)))
+          (PP.ppterm ~metasenv ~subst ~context (List.nth pl (j-2)))
+          (PP.ppterm ~metasenv ~subst ~context p_ty) 
+          (PP.ppterm ~metasenv ~subst ~context exp_p_ty)))); 
+      let res = outtype::arguments@[term] in
+      R.head_beta_reduce (C.Appl res)
+    | C.Match _ -> assert false
+
+  and type_of_branch ~subst context leftno outty cons tycons liftno = 
+    match R.whd ~subst context tycons with
+    | C.Const (Ref.Ref (_,Ref.Ind _)) -> C.Appl [S.lift liftno outty ; cons]
+    | C.Appl (C.Const (Ref.Ref (_,Ref.Ind _))::tl) ->
+        let _,arguments = HExtlib.split_nth leftno tl in
+        C.Appl (S.lift liftno outty::arguments@[cons])
+    | C.Prod (name,so,de) ->
+        let cons =
+         match S.lift 1 cons with
+         | C.Appl l -> C.Appl (l@[C.Rel 1])
+         | t -> C.Appl [t ; C.Rel 1]
+        in
+         C.Prod (name,so,
+           type_of_branch ~subst ((name,(C.Decl so))::context) 
+            leftno outty cons de (liftno+1))
+    | _ -> raise (AssertFailure (lazy "type_of_branch"))
+
+  (* check_metasenv_consistency checks that the "canonical" context of a
+     metavariable is consitent - up to relocation via the relocation list l -
+     with the actual context *)
+  and check_metasenv_consistency 
+    ~subst ~metasenv term context canonical_context l 
+  =
+   match l with
+    | shift, C.Irl n ->
+       let context = snd (HExtlib.split_nth shift context) in
+        let rec compare = function
+         | 0,_,[] -> ()
+         | 0,_,_::_
+         | _,_,[] ->
+            raise (AssertFailure (lazy (Printf.sprintf
+             "Local and canonical context %s have different lengths"
+             (PP.ppterm ~subst ~context ~metasenv term))))
+         | m,[],_::_ ->
+            raise (TypeCheckerFailure (lazy (Printf.sprintf
+             "Unbound variable -%d in %s" m 
+             (PP.ppterm ~subst ~metasenv ~context term))))
+         | m,t::tl,ct::ctl ->
+            (match t,ct with
+                (_,C.Decl t1), (_,C.Decl t2)
+              | (_,C.Def (t1,_)), (_,C.Def (t2,_))
+              | (_,C.Def (_,t1)), (_,C.Decl t2) ->
+                 if not (R.are_convertible ~subst tl t1 t2) then
+                  raise 
+                      (TypeCheckerFailure 
+                        (lazy (Printf.sprintf 
+                      ("Not well typed metavariable local context for %s: " ^^ 
+                       "%s expected, which is not convertible with %s")
+                      (PP.ppterm ~subst ~metasenv ~context term) 
+                      (PP.ppterm ~subst ~metasenv ~context t2) 
+                      (PP.ppterm ~subst ~metasenv ~context t1))))
+              | _,_ ->
+               raise 
+                   (TypeCheckerFailure (lazy (Printf.sprintf 
+                    ("Not well typed metavariable local context for %s: " ^^ 
+                     "a definition expected, but a declaration found")
+                    (PP.ppterm ~subst ~metasenv ~context term)))));
+            compare (m - 1,tl,ctl)
+        in
+         compare (n,context,canonical_context)
+    | shift, lc_kind ->
+       (* we avoid useless lifting by shortening the context*)
+       let l,context = (0,lc_kind), snd (HExtlib.split_nth shift context) in
+       let lifted_canonical_context = 
+         let rec lift_metas i = function
+           | [] -> []
+           | (n,C.Decl t)::tl ->
+               (n,C.Decl (S.subst_meta l (S.lift i t)))::(lift_metas (i+1) tl)
+           | (n,C.Def (t,ty))::tl ->
+               (n,C.Def ((S.subst_meta l (S.lift i t)),
+                          S.subst_meta l (S.lift i ty)))::(lift_metas (i+1) tl)
+         in
+          lift_metas 1 canonical_context in
+       let l = U.expand_local_context lc_kind in
+       try
+        List.iter2 
+        (fun t ct -> 
+          match (t,ct) with
+          | t, (_,C.Def (ct,_)) ->
+             (*CSC: the following optimization is to avoid a possibly expensive
+                    reduction that can be easily avoided and that is quite
+                    frequent. However, this is better handled using levels to
+                    control reduction *)
+             let optimized_t =
+              match t with
+              | C.Rel n ->
+                  (try
+                    match List.nth context (n - 1) with
+                    | (_,C.Def (te,_)) -> S.lift n te
+                    | _ -> t
+                    with Failure _ -> t)
+              | _ -> t
+             in
+             if not (R.are_convertible ~subst context optimized_t ct)
+             then
+               raise 
+                 (TypeCheckerFailure 
+                   (lazy (Printf.sprintf 
+                     ("Not well typed metavariable local context: " ^^ 
+                      "expected a term convertible with %s, found %s")
+                     (PP.ppterm ~subst ~metasenv ~context ct) 
+                     (PP.ppterm ~subst ~metasenv ~context t))))
+          | t, (_,C.Decl ct) ->
+              let type_t = typeof_aux context t in
+              if not (R.are_convertible ~subst context type_t ct) then
+                raise (TypeCheckerFailure 
+                 (lazy (Printf.sprintf 
+                  ("Not well typed metavariable local context: "^^
+                  "expected a term of type %s, found %s of type %s") 
+                  (PP.ppterm ~subst ~metasenv ~context ct) 
+                  (PP.ppterm ~subst ~metasenv ~context t) 
+                  (PP.ppterm ~subst ~metasenv ~context type_t))))
+        ) l lifted_canonical_context 
+       with
+        Invalid_argument _ ->
+          raise (AssertFailure (lazy (Printf.sprintf
+           "Local and canonical context %s have different lengths"
+           (PP.ppterm ~subst ~metasenv ~context term))))
+
+  and check_allowed_sort_elimination ~subst ~metasenv r =
+   let mkapp he arg =
+     match he with
+     | C.Appl l -> C.Appl (l @ [arg])
+     | t -> C.Appl [t;arg] in
+   let rec aux context ind arity1 arity2 =
+    let arity1 = R.whd ~subst context arity1 in
+    let arity2 = R.whd ~subst context arity2 in
+      match arity1,arity2 with
+       | C.Prod (name,so1,de1), C.Prod (_,so2,de2) ->
+          if not (R.are_convertible ~subst context so1 so2) then
+           raise (TypeCheckerFailure (lazy (Printf.sprintf
+            "In outtype: expected %s, found %s"
+            (PP.ppterm ~subst ~metasenv ~context so1)
+            (PP.ppterm ~subst ~metasenv ~context so2)
+            )));
+          aux ((name, C.Decl so1)::context)
+           (mkapp (S.lift 1 ind) (C.Rel 1)) de1 de2
+       | C.Sort _, C.Prod (name,so,ta) ->
+          if not (R.are_convertible ~subst context so ind) then
+           raise (TypeCheckerFailure (lazy (Printf.sprintf
+            "In outtype: expected %s, found %s"
+            (PP.ppterm ~subst ~metasenv ~context ind)
+            (PP.ppterm ~subst ~metasenv ~context so)
+            )));
+          (match arity1, R.whd ~subst ((name,C.Decl so)::context) ta with
+            | (C.Sort C.Type _, C.Sort _)
+            | (C.Sort C.Prop, C.Sort C.Prop) -> ()
+            | (C.Sort C.Prop, C.Sort C.Type _) ->
+        (* TODO: we should pass all these parameters since we
+         * have them already *)
+                let _,leftno,itl,_,i = E.get_checked_indtys r in
+                let itl_len = List.length itl in
+                let _,itname,ittype,cl = List.nth itl i in
+                let cl_len = List.length cl in
+                 (* is it a singleton, non recursive and non informative
+                    definition or an empty one? *)
+                 if not
+                  (cl_len = 0 ||
+                   (itl_len = 1 && cl_len = 1 &&
+                    let _,_,constrty = List.hd cl in
+                      is_non_recursive_singleton r itname ittype constrty &&
+                      is_non_informative leftno constrty))
+                 then
+                  raise (TypeCheckerFailure (lazy
+                   ("Sort elimination not allowed")));
+          | _,_ -> ())
+       | _,_ -> ()
+   in
+    aux 
+
+ in 
+   typeof_aux context term
+
+and eat_prods ~subst ~metasenv context he ty_he args_with_ty = 
+  let rec aux ty_he = function 
+  | [] -> ty_he
+  | (arg, ty_arg)::tl ->
+      match R.whd ~subst context ty_he with 
+      | C.Prod (_,s,t) ->
+          if R.are_convertible ~subst context ty_arg s then
+            aux (S.subst ~avoid_beta_redexes:true arg t) tl
+          else
+            raise 
+              (TypeCheckerFailure 
+                (lazy (Printf.sprintf
+                  ("Appl: wrong application of %s: the parameter %s has type"^^
+                   "\n%s\nbut it should have type \n%s\nContext:\n%s\n")
+                  (PP.ppterm ~subst ~metasenv ~context he)
+                  (PP.ppterm ~subst ~metasenv ~context arg)
+                  (PP.ppterm ~subst ~metasenv ~context ty_arg)
+                  (PP.ppterm ~subst ~metasenv ~context s)
+                  (PP.ppcontext ~subst ~metasenv context))))
+       | _ ->
+          raise 
+            (TypeCheckerFailure
+              (lazy (Printf.sprintf
+                "Appl: %s is not a function, it cannot be applied"
+                (PP.ppterm ~subst ~metasenv ~context
+                 (let res = List.length tl in
+                  let eaten = List.length args_with_ty - res in
+                   (C.Appl
+                    (he::List.map fst
+                     (fst (HExtlib.split_nth eaten args_with_ty)))))))))
+  in
+    aux ty_he args_with_ty
+
+
 (*
 open Printf
 
       check_exp_named_subst_aux metasubst metasenv [] tl ugraph
 
 
-  and sort_of_prod localization_tbl subst metasenv context (name,s) t (t1, t2)
+  and sort_of_prod localize subst metasenv context (name,s) t (t1, t2)
    ugraph
   =
     let module C = Cic in