(**************************************************************************)
(*                                                                        *)
(*  This file is part of WP plug-in of Frama-C.                           *)
(*                                                                        *)
(*  Copyright (C) 2007-2022                                               *)
(*    CEA (Commissariat a l'energie atomique et aux energies              *)
(*         alternatives)                                                  *)
(*                                                                        *)
(*  you can redistribute it and/or modify it under the terms of the GNU   *)
(*  Lesser General Public License as published by the Free Software       *)
(*  Foundation, version 2.1.                                              *)
(*                                                                        *)
(*  It is distributed in the hope that it will be useful,                 *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
(*  GNU Lesser General Public License for more details.                   *)
(*                                                                        *)
(*  See the GNU Lesser General Public License version 2.1                 *)
(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
(*                                                                        *)
(**************************************************************************)

(* -------------------------------------------------------------------------- *)
(* --- Weakest Pre Accumulator                                            --- *)
(* -------------------------------------------------------------------------- *)

open Qed.Logic
open Cil_types
open Lang
open Lang.F

let dkey_pruning = Wp_parameters.register_category "pruning"

(* -------------------------------------------------------------------------- *)
(* --- Category                                                           --- *)
(* -------------------------------------------------------------------------- *)

type category =
  | EMPTY  (** Empty Sequence, equivalent to True, but with State. *)
  | TRUE   (** Logically equivalent to True *)
  | FALSE  (** Logically equivalent to False *)
  | MAYBE  (** Any Hypothesis *)

let c_and c1 c2 =
  match c1 , c2 with
  | FALSE , _ | _ , FALSE -> FALSE
  | MAYBE , _ | _ , MAYBE -> MAYBE
  | TRUE , _  | _ , TRUE -> TRUE
  | EMPTY , EMPTY -> EMPTY

let c_or c1 c2 =
  match c1 , c2 with
  | FALSE , FALSE -> FALSE
  | EMPTY , EMPTY -> EMPTY
  | TRUE , TRUE -> TRUE
  | _ -> MAYBE

let rec cfold_and a f = function
  | [] -> a | e::es -> cfold_and (c_and a (f e)) f es

let rec cfold_or a f = function
  | [] -> a | e::es -> cfold_or (c_or a (f e)) f es

let c_conj f es = cfold_and EMPTY f es
let c_disj f = function [] -> FALSE | e::es -> cfold_or (f e) f es

(* -------------------------------------------------------------------------- *)
(* --- Datatypes                                                          --- *)
(* -------------------------------------------------------------------------- *)

type step = {
  mutable id : int ; (* step identifier *)
  size : int ; (* number of conditions *)
  vars : Vars.t ;
  stmt : stmt option ;
  descr : string option ;
  deps : Property.t list ;
  warn : Warning.Set.t ;
  condition : condition ;
}
and sequence = {
  seq_size : int ;
  seq_vars : Vars.t ;
  seq_core : Pset.t ;
  seq_catg : category ;
  seq_list : step list ; (* forall i . 0 <= i < n ==> Step_i *)
}
and condition =
  | Type of pred (* related to Type *)
  | Have of pred
  | When of pred (* related to Condition *)
  | Core of pred
  | Init of pred
  | Branch of pred * sequence * sequence (* if Pred then Seq_1 else Seq_2 *)
  | Either of sequence list (* exist i . 0 <= i < n && Sequence_i *)
  | State of Mstate.state

(* -------------------------------------------------------------------------- *)
(* --- Variable Utilities                                                 --- *)
(* -------------------------------------------------------------------------- *)

let vars_seqs w = List.fold_left (fun xs s -> Vars.union xs s.seq_vars) Vars.empty w
let vars_list s = List.fold_left (fun xs s -> Vars.union xs s.vars) Vars.empty s
let size_list s = List.fold_left (fun n s -> n + s.size) 0 s
let vars_cond = function
  | Type q | When q | Have q | Core q | Init q -> F.varsp q
  | Branch(p,sa,sb) -> Vars.union (F.varsp p) (Vars.union sa.seq_vars sb.seq_vars)
  | Either cases -> vars_seqs cases
  | State _ -> Vars.empty
let size_cond = function
  | Type _ | When _ | Have _ | Core _ | Init _ | State _ -> 1
  | Branch(_,sa,sb) -> 1 + sa.seq_size + sb.seq_size
  | Either cases -> List.fold_left (fun n s -> n + s.seq_size) 1 cases
let vars_hyp hs = hs.seq_vars
let vars_seq (hs,g) = Vars.union (F.varsp g) hs.seq_vars

(* -------------------------------------------------------------------------- *)
(* --- Core Utilities                                                     --- *)
(* -------------------------------------------------------------------------- *)

let is_core p = match F.e_expr p with
  (*  | Qed.Logic.Eq (a,b) -> is_def a && is_def b *)
  | Qed.Logic.Eq _ -> true
  | _ -> false

let rec add_core s p = match F.p_expr p with
  | Qed.Logic.And ps -> List.fold_left add_core Pset.empty ps
  | _ -> if is_core p then Pset.add p s else s

let core_cond = function
  | Type _ | State _ -> Pset.empty
  | Have p | When p | Core p | Init p -> add_core Pset.empty p
  | Branch(_,sa,sb) -> Pset.inter sa.seq_core sb.seq_core
  | Either [] -> Pset.empty
  | Either (c::cs) ->
    List.fold_left (fun w s -> Pset.inter w s.seq_core) c.seq_core cs

let add_core_step ps s = Pset.union ps (core_cond s.condition)

let core_list s = List.fold_left add_core_step Pset.empty s

(* -------------------------------------------------------------------------- *)
(* --- Category                                                           --- *)
(* -------------------------------------------------------------------------- *)

let catg_seq s = s.seq_catg
let catg_cond = function
  | State _ -> TRUE
  | Have p | Type p | When p | Core p | Init p ->
    begin
      match F.is_ptrue p with
      | No -> FALSE
      | Maybe -> MAYBE
      | Yes -> EMPTY
    end
  | Either cs -> c_disj catg_seq cs
  | Branch(_,a,b) -> c_or a.seq_catg b.seq_catg
let catg_step s = catg_cond s.condition
let catg_list l = c_conj catg_step l

(* -------------------------------------------------------------------------- *)
(* --- Sequence Constructor                                               --- *)
(* -------------------------------------------------------------------------- *)

let sequence l = {
  seq_size = size_list l ;
  seq_vars = vars_list l ;
  seq_core = core_list l ;
  seq_catg = catg_list l ;
  seq_list = l ;
}

(* -------------------------------------------------------------------------- *)
(* --- Sequence Comparator                                                --- *)
(* -------------------------------------------------------------------------- *)

let rec equal_cond ca cb =
  match ca,cb with
  | State _ , State _ -> true
  | Type p , Type q
  | Have p , Have q
  | When p , When q
  | Core p , Core q
  | Init p , Init q
    -> p == q
  | Branch(p,a,b) , Branch(q,a',b') ->
    p == q && equal_seq a a' && equal_seq b b'
  | Either u, Either v ->
    Qed.Hcons.equal_list equal_seq u v
  | State _ , _ | _ , State _
  | Type _ , _ | _ , Type _
  | Have _ , _ | _ , Have _
  | When _ , _ | _ , When _
  | Core _ , _ | _ , Core _
  | Init _ , _ | _ , Init _
  | Branch _ , _ | _ , Branch _
    -> false

and equal_step a b =
  equal_cond a.condition b.condition

and equal_list sa sb =
  Qed.Hcons.equal_list equal_step sa sb

and equal_seq sa sb =
  equal_list sa.seq_list sb.seq_list

(* -------------------------------------------------------------------------- *)
(* --- Core Inference                                                     --- *)
(* -------------------------------------------------------------------------- *)

module Core =
struct

  let rec fpred core p = match F.p_expr p with
    | Qed.Logic.And ps -> F.p_conj (List.map (fpred core) ps)
    | _ -> if Pset.mem p core then p_true else p

  let fcond core = function
    | Core p -> Core (fpred core p)
    | Have p -> Have (fpred core p)
    | When p -> When (fpred core p)
    | Init p -> Init (fpred core p)
    | (Type _ | Branch _ | Either _ | State _) as cond -> cond

  let fstep core step =
    let condition = fcond core step.condition in
    let vars = vars_cond condition in
    { step with condition ; vars }

  let factorize a b =
    if Wp_parameters.Core.get () then
      let core = Pset.inter a.seq_core b.seq_core in
      if Pset.is_empty core then None else
        let ca = List.map (fstep core) a.seq_list in
        let cb = List.map (fstep core) b.seq_list in
        Some (F.p_conj (Pset.elements core) , sequence ca , sequence cb)
    else None

end

(* -------------------------------------------------------------------------- *)
(* --- Bundle (non-simplified conditions)                                 --- *)
(* -------------------------------------------------------------------------- *)

module Bundle :
sig
  type t
  val empty : t
  val vars : t -> Vars.t
  val is_empty : t -> bool
  val category : t -> category
  val add : step -> t -> t
  val factorize : t -> t -> t * t * t
  val big_inter : t list -> t
  val diff : t -> t -> t
  val head : t -> Mstate.state option
  val freeze: ?join:step -> t -> sequence
  val map : (condition -> 'a) -> t -> 'a list
end =
struct
  module SEQ = Qed.Listset.Make
      (struct
        type t = int * step
        let equal (k1,_) (k2,_) = k1 = k2
        let compare (k1,s1) (k2,s2) =
          let rank = function
            | Type _ -> 0
            | When _ -> 1
            | _ -> 2
          in
          let r = rank s1.condition - rank s2.condition in
          if r = 0 then Stdlib.compare k2 k1 else r
      end)

  type t = Vars.t * SEQ.t

  let vars = fst
  let cid = ref 0
  let fresh () = incr cid ; assert (!cid > 0) ; !cid

  let add s (xs,t) = Vars.union xs s.vars , SEQ.add (fresh (),s) t

  let empty = Vars.empty , []
  let is_empty = function (_,[]) -> true | _ -> false

  let head = function _,(_,{ condition = State s }) :: _ -> Some s | _ -> None

  let build seq =
    let xs = List.fold_left
        (fun xs (_,s) -> Vars.union xs s.vars) Vars.empty seq in
    xs , seq

  let factorize (_,a) (_,b) =
    let l,m,r = SEQ.factorize a b in
    build l , build m , build r

  let big_inter cs = build (SEQ.big_inter (List.map snd cs))
  let diff (_,a) (_,b) = build (SEQ.diff a b)
  let freeze ?join (seq_vars,bundle) =
    let seq = List.map snd bundle in
    let seq_list = match join with None -> seq | Some s -> seq @ [s] in
    let seq_size = size_list seq in
    let seq_catg = catg_list seq in
    { seq_size ; seq_vars ; seq_core = Pset.empty ; seq_catg ; seq_list }
  let map f b = List.map (fun (_,s) -> f s.condition) (snd b)
  let category (_,bundle) = c_conj (fun (_,s) -> catg_step s) bundle
end

(* -------------------------------------------------------------------------- *)
(* --- Hypotheses                                                         --- *)
(* -------------------------------------------------------------------------- *)

type bundle = Bundle.t
type sequent = sequence * F.pred

let pretty = ref (fun _fmt _seq -> ())

let is_true = function { seq_catg = TRUE | EMPTY } -> true | _ -> false
let is_empty = function { seq_catg = EMPTY } -> true | _ -> false
let is_false = function { seq_catg = FALSE } -> true | _ -> false

let is_absurd_h h = match h.condition with
  | (Type p | Core p | When p | Have p | Init p) -> p == F.p_false
  | Branch(_,p,q) -> is_false p && is_false q
  | Either w -> List.for_all is_false w (* note: an empty w is an absurd hyp *)
  | State _ -> false

let is_trivial_h h = match h.condition with
  | State _ -> false
  | (Type p | Core p | When p | Have p | Init p) -> p == F.p_true
  | Branch(_,a,b) -> is_true a && is_true b
  | Either [] -> false
  | Either w -> List.for_all is_true w

let is_trivial_hs_p hs p = p == F.p_true || List.exists is_absurd_h hs
let is_trivial_hsp (hs,p) = is_trivial_hs_p hs p
let is_trivial (s:sequent) = is_trivial_hs_p (fst s).seq_list (snd s)

(* -------------------------------------------------------------------------- *)
(* --- Extraction                                                         --- *)
(* -------------------------------------------------------------------------- *)

let rec pred_cond = function
  | State _ -> F.p_true
  | When p | Type p | Have p | Core p | Init p -> p
  | Branch(p,a,b) -> F.p_if p (pred_seq a) (pred_seq b)
  | Either cases -> F.p_any pred_seq cases
and pred_seq seq = F.p_all (fun s -> pred_cond s.condition) seq.seq_list
let extract bundle = Bundle.map pred_cond bundle
let bundle = Bundle.freeze ?join:None

let intersect p bundle = Vars.intersect (F.varsp p) (Bundle.vars bundle)
let occurs x bundle = Vars.mem x (Bundle.vars bundle)

(* -------------------------------------------------------------------------- *)
(* --- Constructors                                                       --- *)
(* -------------------------------------------------------------------------- *)

let nil = Bundle.empty
let noid = (-1)

let step ?descr ?stmt ?(deps=[]) ?(warn=Warning.Set.empty) cond =
  {
    id = noid ;
    size = size_cond cond ;
    vars = vars_cond cond ;
    stmt = stmt ;
    descr = descr ;
    warn = warn ;
    deps = deps ;
    condition = cond ;
  }

let update_cond ?descr ?(deps=[]) ?(warn=Warning.Set.empty) h c =
  let descr = match h.descr, descr  with
    | None, _ -> descr ;
    | Some _, None -> h.descr ;
    | Some decr1, Some descr2 -> Some (decr1 ^ "-" ^ descr2)
  in {
    id = noid ;
    condition = c ;
    stmt = h.stmt ;
    size = size_cond c ;
    vars = vars_cond c ;
    descr = descr ;
    deps = deps@h.deps ;
    warn = Warning.Set.union h.warn warn ;
  }

type 'a disjunction = D_TRUE | D_FALSE | D_EITHER of 'a list

let disjunction phi es =
  let positives = ref false in (* TRUE or EMPTY items *)
  let remains = List.filter
      (fun e ->
         match phi e with
         | TRUE | EMPTY -> positives := true ; false
         | MAYBE -> true
         | FALSE -> false
      ) es in
  match remains with
  | [] -> if !positives then D_TRUE else D_FALSE
  | cs -> D_EITHER cs

(* -------------------------------------------------------------------------- *)
(* --- Prenex-Form Introduction                                           --- *)
(* -------------------------------------------------------------------------- *)

let prenex_intro p =
  try
    let open Qed.Logic in
    (* invariant: xs <> []; result <-> forall xs, hs -> p *)
    let rec walk hs xs p =
      match F.p_expr p with
      | Imply(h,p) -> walk (h::hs) xs p
      | Bind(Forall,_,_) -> bind hs xs p
      | _ ->
        if hs = [] then raise Exit ;
        F.p_forall (List.rev xs) (F.p_hyps (List.concat hs) p)
    (* invariant: result <-> forall hs xs (\tau.bind) *)
    and bind hs xs p =
      let pool = Lang.get_pool () in
      let ctx,t = e_open ~pool ~forall:true
          ~exists:false ~lambda:false (e_prop p) in
      let xs = List.fold_left (fun xs (_,x) -> x::xs) xs (List.rev ctx) in
      walk hs xs (F.p_bool t)
    (* invariant: result <-> p *)
    and crawl p =
      match F.p_expr p with
      | Imply(h,p) -> F.p_hyps h (crawl p)
      | Bind(Forall,_,_) -> bind [] [] p
      | _ -> raise Exit
    in crawl p
  with Exit -> p

(* -------------------------------------------------------------------------- *)
(* --- Existential Introduction                                           --- *)
(* -------------------------------------------------------------------------- *)

let rec exist_intro p =
  let open Qed.Logic in
  match F.p_expr p with
  | And ps -> F.p_all exist_intro ps
  | Bind(Exists,_,_) ->
    let pool = Lang.get_pool () in
    let _,t = e_open ~pool ~exists:true
        ~forall:false ~lambda:false (e_prop p) in
    exist_intro (F.p_bool t)
  | _ -> (* Note: Qed implement De Morgan rules
            such that [p] cannot match the
            decomposable representations:
            Not Or, Not Imply, Not Forall *)
    if Wp_parameters.Prenex.get ()
    then prenex_intro p
    else p

let rec exist_intros = function
  | [] -> []
  | p::hs -> begin
      let open Qed.Logic in
      match F.p_expr p with
      | And ps -> exist_intros (ps@hs)
      | Bind(Exists,_,_) ->
        let pool = Lang.get_pool () in
        let _,t = F.QED.e_open ~pool ~exists:true
            ~forall:false ~lambda:false (e_prop p) in
        exist_intros ((F.p_bool t)::hs)
      | _ -> (* Note: Qed implement De Morgan rules
                such that [p] cannot match the
                decomposable representations:
                Not Or, Not Imply, Not Forall *)
        p::(exist_intros hs)
    end

(* -------------------------------------------------------------------------- *)
(* --- Universal Introduction                                            --- *)
(* -------------------------------------------------------------------------- *)

let rec forall_intro p =
  let open Qed.Logic in
  match F.p_expr p with
  | Bind(Forall,_,_) ->
    let pool = Lang.get_pool () in
    let _,t = F.QED.e_open ~pool ~forall:true
        ~exists:false ~lambda:false (e_prop p) in
    forall_intro (F.p_bool t)
  | Imply(hs,p) ->
    let hs = exist_intros hs in
    let hp,p = forall_intro p in
    hs @ hp , p
  | Or qs -> (* analogy with Imply *)
    let hps,ps = List.fold_left (fun (hs,ps) q ->
        let hp,p = forall_intro q in (* q <==> (hp ==> p) *)
        (hp @ hs), (p::ps)) ([],[]) qs
    in (* ORs qs  <==> ORs (hps ==> ps)
                  <==> ((ANDs hps) ==> ORs ps) *)
    let hps,ps = List.fold_left (fun (hs,ps) q ->
        match F.repr (F.e_prop q) with
        | Neq _ -> ((F.p_not q)::hs), ps
        | _ -> hs, (q::ps)) (hps,[]) ps
    in (* ORs qs <==> ((ANDs hps) ==> ORs ps)) *)
    hps, (F.p_disj ps)
  | _ -> (* Note: Qed implement De Morgan rules
            such that [p] cannot match the
            decomposable representations:
            Not And, Not Exists *)
    [] , p

(* -------------------------------------------------------------------------- *)
(* --- Constructors                                                       --- *)
(* -------------------------------------------------------------------------- *)

type 'a attributed =
  ( ?descr:string ->
    ?stmt:stmt ->
    ?deps:Property.t list ->
    ?warn:Warning.Set.t ->
    'a )

let domain ps hs =
  if ps = [] then hs else
    Bundle.add (step (Type (p_conj ps))) hs

let intros ps hs =
  if ps = [] then hs else
    let p = F.p_all exist_intro ps in
    Bundle.add (step ~descr:"Goal" (When p)) hs

let state ?descr ?stmt state hs =
  let cond = State state in
  let s = step ?descr ?stmt cond in
  Bundle.add s hs

let assume ?descr ?stmt ?deps ?warn ?(init=false) ?(domain=false) p hs =
  match F.is_ptrue p with
  | Yes -> hs
  | No ->
    let cond = if init then Init p else if domain then Type p else Have p in
    let s = step ?descr ?stmt ?deps ?warn cond in
    Bundle.add s Bundle.empty
  | Maybe ->
    begin
      match Bundle.category hs with
      | MAYBE | TRUE | EMPTY ->
        let p = exist_intro p in
        let cond =
          if init then Init p else if domain then Type p else Have p in
        let s = step ?descr ?stmt ?deps ?warn cond in
        Bundle.add s hs
      | FALSE -> hs
    end

let join = function None -> None | Some s -> Some (step (State s))

let branch ?descr ?stmt ?deps ?warn p ha hb =
  match F.is_ptrue p with
  | Yes -> ha
  | No -> hb
  | Maybe ->
    match Bundle.category ha , Bundle.category hb with
    | TRUE , TRUE -> Bundle.empty
    | _ , FALSE -> assume ?descr ?stmt ?deps ?warn p ha
    | FALSE , _ -> assume ?descr ?stmt ?deps ?warn (p_not p) hb
    | _ ->
      let ha,hs,hb = Bundle.factorize ha hb in
      if Bundle.is_empty ha && Bundle.is_empty hb then hs else
        let join = join (Bundle.head hs) in
        let a = Bundle.freeze ?join ha in
        let b = Bundle.freeze ?join hb in
        let s = step ?descr ?stmt ?deps ?warn (Branch(p,a,b)) in
        Bundle.add s hs

let either ?descr ?stmt ?deps ?warn cases =
  match disjunction Bundle.category cases with
  | D_TRUE -> Bundle.empty
  | D_FALSE ->
    let s = step ?descr ?stmt ?deps ?warn (Have p_false) in
    Bundle.add s Bundle.empty
  | D_EITHER cases ->
    let trunk = Bundle.big_inter cases in
    let cases = List.map (fun case -> Bundle.diff case trunk) cases in
    match disjunction Bundle.category cases with
    | D_TRUE -> trunk
    | D_FALSE ->
      let s = step ?descr ?stmt ?deps ?warn (Have p_false) in
      Bundle.add s Bundle.empty
    | D_EITHER cases ->
      let cases = List.map Bundle.freeze cases in
      let s = step ?descr ?stmt ?deps ?warn (Either cases) in
      Bundle.add s trunk

let merge cases = either ~descr:"Merge" cases

(* -------------------------------------------------------------------------- *)
(* --- Flattening                                                         --- *)
(* -------------------------------------------------------------------------- *)

let rec flat_catg = function
  | [] -> EMPTY
  | s::cs ->
    match catg_step s with
    | EMPTY -> flat_catg cs
    | r -> r

let flat_cons step tail =
  match flat_catg tail with
  | FALSE -> tail
  | _ -> step :: tail

let flat_concat head tail =
  match flat_catg head with
  | EMPTY -> tail
  | FALSE -> head
  | MAYBE|TRUE ->
    match flat_catg tail with
    | EMPTY -> head
    | FALSE -> tail
    | MAYBE|TRUE -> head @ tail

let core_residual step core = {
  id = noid ;
  size = 1 ;
  vars = F.varsp core ;
  condition = Core core ;
  descr = None ;
  warn = Warning.Set.empty ;
  deps = [] ;
  stmt = step.stmt ;
}

let core_branch step p a b =
  let condition =
    match a.seq_catg , b.seq_catg with
    | (TRUE | EMPTY) , (TRUE|EMPTY) -> Have p_true
    | FALSE , FALSE -> Have p_false
    | _ -> Branch(p,a,b)
  in update_cond step condition

let rec flatten_sequence m = function
  | [] -> []
  | step :: seq ->
    match step.condition with
    | State _ -> flat_cons step (flatten_sequence m seq)
    | Have p | Type p | When p | Core p | Init p ->
      begin
        match F.is_ptrue p with
        | Yes -> m := true ; flatten_sequence m seq
        | No -> (* FALSE context *) if seq <> [] then m := true ; [step]
        | Maybe -> flat_cons step (flatten_sequence m seq)
      end
    | Branch(p,a,b) ->
      begin
        match F.is_ptrue p with
        | Yes -> m := true ; flat_concat a.seq_list (flatten_sequence m seq)
        | No -> m := true ; flat_concat b.seq_list (flatten_sequence m seq)
        | Maybe ->
          let sa = a.seq_list in
          let sb = b.seq_list in
          match a.seq_catg , b.seq_catg with
          | (TRUE|EMPTY) , (TRUE|EMPTY) ->
            m := true ; flatten_sequence m seq
          | _ , FALSE ->
            m := true ;
            let step = update_cond step (Have p) in
            step :: sa @ flatten_sequence m seq
          | FALSE , _ ->
            m := true ;
            let step = update_cond step (Have (p_not p)) in
            step :: sb @ flatten_sequence m seq
          | _ ->
            begin
              match Core.factorize a b with
              | None -> step :: flatten_sequence m seq
              | Some( core , a , b ) ->
                m := true ;
                let score = core_residual step core in
                let scond = core_branch step p a b in
                score :: scond :: flatten_sequence m seq
            end
      end
    | Either [] -> (* FALSE context *) if seq <> [] then m := true ; [step]
    | Either cases ->
      match disjunction catg_seq cases with
      | D_TRUE -> m := true ; flatten_sequence m seq
      | D_FALSE -> m := true ; [ update_cond step (Have p_false) ]
      | D_EITHER [hc] ->
        m := true ; flat_concat hc.seq_list (flatten_sequence m seq)
      | D_EITHER cs ->
        let step = update_cond step (Either cs) in
        flat_cons step (flatten_sequence m seq)

(* -------------------------------------------------------------------------- *)
(* --- Mapping                                                            --- *)
(* -------------------------------------------------------------------------- *)

let rec map_condition f = function
  | State s -> State (Mstate.apply (F.p_lift f) s)
  | Have p -> Have (f p)
  | Type p -> Type (f p)
  | When p -> When (f p)
  | Core p -> Core (f p)
  | Init p -> Init (f p)
  | Branch(p,a,b) -> Branch(f p,map_sequence f a,map_sequence f b)
  | Either cs -> Either (List.map (map_sequence f) cs)

and map_step f h = update_cond h (map_condition f h.condition)

and map_steplist f = function
  | [] -> []
  | h::hs ->
    let h = map_step f h in
    if is_absurd_h h then [h] else
      let hs = map_steplist f hs in
      if is_trivial_h h then hs else h :: hs

and map_sequence f s =
  sequence (map_steplist f s.seq_list)

and map_sequent f (hs,g) = map_sequence f hs , f g

(* -------------------------------------------------------------------------- *)
(* --- Ground Simplifier                                                  --- *)
(* -------------------------------------------------------------------------- *)

module Ground = Letify.Ground

let rec ground_flow ~fwd env h =
  match h.condition with
  | State s ->
    let s = Mstate.apply (Ground.e_apply env) s in
    update_cond h (State s)
  | Type _ | Have _ | When _ | Core _ | Init _ ->
    let phi = if fwd then Ground.forward else Ground.backward in
    let cond = map_condition (phi env) h.condition in
    update_cond h cond
  | Branch(p,a,b) ->
    let p,wa,wb = Ground.branch env p in
    let a = ground_flowseq ~fwd wa a in
    let b = ground_flowseq ~fwd wb b in
    update_cond h (Branch(p,a,b))
  | Either ws ->
    let ws = List.map
        (fun w -> ground_flowseq ~fwd (Ground.copy env) w) ws in
    update_cond h (Either ws)

and ground_flowseq ~fwd env hs =
  sequence (ground_flowlist ~fwd env hs.seq_list)

and ground_flowlist ~fwd env hs =
  if fwd
  then ground_flowdir ~fwd env hs
  else List.rev (ground_flowdir ~fwd env (List.rev hs))

and ground_flowdir ~fwd env = function
  | [] -> []
  | h::hs ->
    let h = ground_flow ~fwd env h in
    if is_absurd_h h then [h] else
      let hs = ground_flowdir ~fwd env hs in
      if is_trivial_h h then hs else h :: hs

let ground (hs,g) =
  let hs = ground_flowlist ~fwd:true (Ground.top ()) hs in
  let hs = ground_flowlist ~fwd:false (Ground.top ()) hs in
  let env = Ground.top () in
  let hs = ground_flowlist ~fwd:true env hs in
  hs , Ground.p_apply env g

(* -------------------------------------------------------------------------- *)
(* --- Letify                                                             --- *)
(* -------------------------------------------------------------------------- *)

module Sigma = Letify.Sigma
module Defs = Letify.Defs

let used_of_dseq ds =
  Array.fold_left (fun ys (_,step) -> Vars.union ys step.vars) Vars.empty ds

let bind_dseq target (di,_) sigma =
  Letify.bind (Letify.bind sigma di target) di (Defs.domain di)

let locals sigma ~target ~required ?(step=Vars.empty) k dseq =
  (* returns ( target , export ) *)
  let t = ref target in
  let e = ref (Vars.union required step) in
  Array.iteri
    (fun i (_,step) ->
       if i > k then t := Vars.union !t step.vars ;
       if i <> k then e := Vars.union !e step.vars ;
    ) dseq ;
  Vars.diff !t (Sigma.domain sigma) , !e

let dseq_of_step sigma step =
  let defs =
    match step.condition with
    | Init p | Have p | When p | Core p -> Defs.extract (Sigma.p_apply sigma p)
    | Type _ | Branch _ | Either _ | State _ -> Defs.empty
  in defs , step

let letify_assume sref (_,step) =
  let current = !sref in
  begin
    match step.condition with
    | Type _ | Branch _ | Either _ | State _ -> ()
    | Init p | Have p | When p | Core p ->
      if Wp_parameters.Simpl.get () then
        sref := Sigma.assume current p
  end ; current

let rec letify_type sigma used p = match F.p_expr p with
  | And ps -> p_all (letify_type sigma used) ps
  | _ ->
    let p = Sigma.p_apply sigma p in
    let vs = F.varsp p in
    if Vars.intersect used vs || Vars.is_empty vs then p else F.p_true

let rec letify_seq sigma0 ~target ~export (seq : step list) =
  let dseq = Array.map (dseq_of_step sigma0) (Array.of_list seq) in
  let sigma1 = Array.fold_right (bind_dseq target) dseq sigma0 in
  let sref = ref sigma1 in (* with definitions *)
  let dsigma = Array.map (letify_assume sref) dseq in
  let sigma2 = !sref in (* with assumptions *)
  let outside = Vars.union export target in
  let inside = used_of_dseq dseq in
  let used = Vars.diff (Vars.union outside inside) (Sigma.domain sigma2) in
  let required = Vars.union outside (Sigma.codomain sigma2) in
  let sequence =
    Array.mapi (letify_step dseq dsigma ~used ~required ~target) dseq in
  let modified = ref (not (Sigma.equal sigma0 sigma1)) in
(*
  let sequence =
    if Wp_parameters.Ground.get () then fst (ground_hrp sequence)
    else sequence in
*)
  let sequence = flatten_sequence modified (Array.to_list sequence) in
  !modified , sigma1 , sigma2 , sequence

and letify_step dseq dsigma ~required ~target ~used i (d,s) =
  let sigma = dsigma.(i) in
  let cond = match s.condition with
    | State s -> State (Mstate.apply (Sigma.e_apply sigma) s)
    | Init p ->
      let p = Sigma.p_apply sigma p in
      let ps = Letify.add_definitions sigma d required [p] in
      Init (p_conj ps)
    | Have p ->
      let p = Sigma.p_apply sigma p in
      let ps = Letify.add_definitions sigma d required [p] in
      Have (p_conj ps)
    | Core p ->
      let p = Sigma.p_apply sigma p in
      let ps = Letify.add_definitions sigma d required [p] in
      Core (p_conj ps)
    | When p ->
      let p = Sigma.p_apply sigma p in
      let ps = Letify.add_definitions sigma d required [p] in
      When (p_conj ps)
    | Type p ->
      Type (letify_type sigma used p)
    | Branch(p,a,b) ->
      let p = Sigma.p_apply sigma p in
      let step = F.varsp p in
      let (target,export) = locals sigma ~target ~required ~step i dseq in
      let sa = Sigma.assume sigma p in
      let sb = Sigma.assume sigma (p_not p) in
      let a = letify_case sa ~target ~export a in
      let b = letify_case sb ~target ~export b in
      Branch(p,a,b)
    | Either cases ->
      let (target,export) = locals sigma ~target ~required i dseq in
      Either (List.map (letify_case sigma ~target ~export) cases)
  in update_cond s cond

and letify_case sigma ~target ~export seq =
  let (_,_,_,s) = letify_seq sigma ~target ~export seq.seq_list
  in sequence s

(* -------------------------------------------------------------------------- *)
(* --- External Simplifier                                                --- *)
(* -------------------------------------------------------------------------- *)

let equivalent_exp solvers e =
  List.fold_left (fun e s -> s#equivalent_exp e) e solvers
let stronger_goal solvers p =
  List.fold_left (fun p s -> s#stronger_goal p) p solvers
let weaker_hyp solvers p =
  List.fold_left (fun p s -> s#weaker_hyp p) p solvers
let equivalent_branch solvers p =
  List.fold_left (fun p s -> s#equivalent_branch p) p solvers

let apply_goal solvers p =
  let stronger_and_then_assume p =
    let p' = stronger_goal solvers p in
    List.iter (fun s -> s#assume (p_not p')) solvers;
    p'
  in
  match F.p_expr p with
  | Or ps ->
    let unmodified,qs = List.fold_left (fun (unmodified,qs) p ->
        let p' = stronger_and_then_assume p in
        (unmodified && (Lang.F.eqp p p')), (p'::qs))
        (true,[]) ps
    in if unmodified then p else p_disj qs
  | _ -> stronger_and_then_assume p

let apply_hyp modified solvers h =
  let weaken_and_then_assume p =
    let p' = weaker_hyp solvers p in
    if not (Lang.F.eqp p p') then modified := true;
    List.iter (fun s -> s#assume p') solvers; p'
  in
  let weaken p = match F.p_expr p with
    | And ps ->
      let unmodified,qs = List.fold_left (fun (unmodified,qs) p ->
          let p' = weaken_and_then_assume p in
          (unmodified && (Lang.F.eqp p p')), (p'::qs))
          (true,[]) ps
      in if unmodified then p else p_conj qs
    | _ -> weaken_and_then_assume p
  in
  match h.condition with
  | State s -> update_cond h (State (Mstate.apply (equivalent_exp solvers) s))
  | Init p -> update_cond h (Init (weaken p))
  | Type p -> update_cond h (Type (weaken p))
  | Have p -> update_cond h (Have (weaken p))
  | When p -> update_cond h (When (weaken p))
  | Core p -> update_cond h (Core (weaken p))
  | Branch(p,_,_) -> List.iter (fun s -> s#target p) solvers; h
  | Either _ -> h

let decide_branch modified solvers h =
  match h.condition with
  | Branch(p,a,b) ->
    let q = equivalent_branch solvers p in
    if q != p then
      ( modified := true ; update_cond h (Branch(q,a,b)) )
    else h
  | _ -> h

let add_infer modified s hs =
  let p = p_conj s#infer in
  if p != p_true then
    ( modified := true ; step ~descr:s#name (Have p) :: hs )
  else
    hs

type outcome =
  | NoSimplification
  | Simplified of hsp
  | Trivial

and hsp = step list * pred

let apply_simplifiers (solvers : simplifier list) (hs,g) =
  if solvers = [] then NoSimplification
  else
    try
      let modified = ref false in
      let solvers = List.map (fun s -> s#copy) solvers in
      let hs = List.map (apply_hyp modified solvers) hs in
      List.iter (fun s -> s#target g) solvers ;
      List.iter (fun s -> s#fixpoint) solvers ;
      let hs = List.map (decide_branch modified solvers) hs in
      let hs = List.fold_right (add_infer modified) solvers hs in
      let p = apply_goal solvers g in
      if p != g || !modified then
        Simplified (hs,p)
      else
        NoSimplification
    with Contradiction ->
      Trivial

(* -------------------------------------------------------------------------- *)
(* --- Sequence Builder                                                   --- *)
(* -------------------------------------------------------------------------- *)

let empty = {
  seq_size = 0 ;
  seq_vars = Vars.empty ;
  seq_core = Pset.empty ;
  seq_catg = EMPTY ;
  seq_list = [] ;
}

let trivial = empty , F.p_true

let append sa sb =
  if sa.seq_size = 0 then sb else
  if sb.seq_size = 0 then sa else
    let seq_size = sa.seq_size + sb.seq_size in
    let seq_vars = Vars.union sa.seq_vars sb.seq_vars in
    let seq_core = Pset.union sa.seq_core sb.seq_core in
    let seq_list = sa.seq_list @ sb.seq_list in
    let seq_catg = c_and sa.seq_catg sb.seq_catg in
    { seq_size ; seq_vars ; seq_core ; seq_catg ; seq_list }

let concat slist =
  if slist = [] then empty else
    let seq_size = List.fold_left (fun n s -> n + s.seq_size) 0 slist in
    let seq_list = List.concat (List.map (fun s -> s.seq_list) slist) in
    let seq_vars = List.fold_left (fun w s -> Vars.union w s.seq_vars)
        Vars.empty slist in
    let seq_core = List.fold_left (fun w s -> Pset.union w s.seq_core)
        Pset.empty slist in
    let seq_catg = c_conj catg_seq slist in
    { seq_size ; seq_vars ; seq_core ; seq_catg ; seq_list }

let seq_branch ?stmt p sa sb =
  sequence [step ?stmt (Branch(p,sa,sb))]

(* -------------------------------------------------------------------------- *)
(* --- Introduction Utilities                                             --- *)
(* -------------------------------------------------------------------------- *)

let lemma g =
  let cc g =
    let hs,p = forall_intro g in
    let hs = List.map (fun p -> step (Have p)) hs in
    sequence hs , p
  in Lang.local ~vars:(F.varsp g) cc g

let introduction (hs,g) =
  let flag = ref false in
  let intro p = let q = exist_intro p in if q != p then flag := true ; q in
  let hj = List.map (map_step intro) hs.seq_list in
  let hi,p = forall_intro g in
  let hi = List.map (fun p -> step (Have p)) hi in
  if not !flag && hi == [] then
    if p == g then None else Some (hs , p)
  else
    Some (sequence (hi @ hj) , p)

let introduction_eq s = match introduction s with
  | Some s' -> s'
  | None -> s

(* -------------------------------------------------------------------------- *)
(* --- Constant Folder                                                    --- *)
(* -------------------------------------------------------------------------- *)

module ConstantFolder =
struct

  open Qed

  type sigma = {
    mutable cst : bool Tmap.t ;
    mutable dom : Vars.t ; (* support of defs *)
    mutable def : term Tmap.t ; (* defs *)
    mutable cache : F.sigma option ; (* memo *)
  }

  let rec is_cst s e = match F.repr e with
    | True | False | Kint _ | Kreal _ -> true
    | Fun(_,es) ->
      begin
        try Tmap.find e s.cst
        with Not_found ->
          let cst = List.for_all (is_cst s) es in
          s.cst <- Tmap.add e cst s.cst ; cst
      end
    | _ -> false

  let set_def s p a e =
    try
      let e0 = Tmap.find a s.def in
      match F.is_true (F.e_eq e e0) with
      | Logic.Yes -> ()
      | Logic.No -> raise Contradiction
      | Logic.Maybe ->
        if F.compare e e0 < 0 then s.def <- Tmap.add a e s.def
    with Not_found ->
      begin
        s.dom <- Vars.union (F.vars a) s.dom ;
        s.def <- Tmap.add a e s.def ;
        s.def <- Tmap.add p p s.def ;
        s.cache <- None ;
      end

  let collect_set_def s p = Lang.iter_consequence_literals
      (fun literal -> match Lang.F.repr literal with
         | Logic.Eq(a,b) ->
           if is_cst s a then set_def s literal b a ;
           if is_cst s b then set_def s literal a b ;
         | _ -> ()) p

  let collect s = function
    | Have p | When p | Core p | Init p -> collect_set_def s (F.e_prop p)
    | Type _ | Branch _ | Either _ | State _ -> ()

  let subst s =
    match s.cache with
    | Some m -> m
    | None ->
      let m = Lang.sigma () in
      F.Subst.add_fun m (fun e -> Tmap.find e s.def) ;
      s.cache <- Some m ; m

  let e_apply s e = F.e_subst (subst s) e
  let p_apply s p = F.p_subst (subst s) p

  let rec c_apply s = function
    | State m -> State (Mstate.apply (e_apply s) m)
    | Type p -> Type (p_apply s p)
    | Init p -> Init (p_apply s p)
    | Have p -> Have (p_apply s p)
    | When p -> When (p_apply s p)
    | Core p -> Core (p_apply s p)
    | Branch(p,sa,sb) -> Branch( p_apply s p , seq_apply s sa , seq_apply s sb )
    | Either cs -> Either (List.map (seq_apply s) cs)

  and s_apply s (step : step) : step =
    update_cond step (c_apply s step.condition)

  and seq_apply s seq =
    sequence (List.map (s_apply s) seq.seq_list)

  let simplify (hs,p) =
    let s = {
      cst = Tmap.empty ;
      def = Tmap.empty ;
      dom = Vars.empty ;
      cache = None ;
    } in
    try
      List.iter (fun h -> collect s h.condition) hs ;
      let hs = List.map (s_apply s) hs in
      let p = p_apply s p in
      hs , p
    with Contradiction ->
      [] , F.p_true

end

(* -------------------------------------------------------------------------- *)
(* --- Letify-Fixpoint                                                    --- *)
(* -------------------------------------------------------------------------- *)

let rec fixpoint limit solvers sigma s0 =
  if limit > 0 then compute limit solvers sigma s0 else s0
and compute limit solvers sigma s0 =
  Db.yield ();
  let s1 =
    if Wp_parameters.Ground.get () then ground s0
    else s0 in
  let hs,p = ConstantFolder.simplify s1 in
  let target = F.varsp p in
  let export = Vars.empty in
  let modified , sigma1 , sigma2 , hs =
    letify_seq sigma ~target ~export hs in
  let p = Sigma.p_apply sigma2 p in
  let s2 = ground (hs , p) in
  if is_trivial_hsp s2 then [],p_true
  else
  if modified || (limit > 0 && not (equal_list (fst s0) (fst s2)))
  then fixpoint (pred limit) solvers sigma1 s2
  else
    match apply_simplifiers solvers s2 with
    | Simplified s3 -> fixpoint (pred limit) solvers sigma1 s3
    | Trivial -> [],p_true
    | NoSimplification -> s2

let letify_hsp ?(solvers=[]) hsp = fixpoint 10 solvers Sigma.empty hsp

let rec simplify ?(solvers=[]) ?(intros=10) (seq,p0) =
  let hs,p = fixpoint 10 solvers Sigma.empty (seq.seq_list,p0) in
  let sequent = sequence hs , p in
  match introduction sequent with
  | Some introduced ->
    if intros > 0 then
      simplify ~solvers ~intros:(pred intros) introduced
    else introduced
  | None ->
    sequent

(* -------------------------------------------------------------------------- *)
(* --- Pruning                                                            --- *)
(* -------------------------------------------------------------------------- *)

let residual p = {
  id = noid ;
  size = 1 ;
  vars = F.varsp p ;
  stmt = None ;
  descr = Some "Residual" ;
  deps = [] ;
  warn = Warning.Set.empty ;
  condition = When p ;
}

let rec add_case p = function
  | ( { condition = (Type _) } as step ):: tail ->
    step :: add_case p tail
  | hs -> residual p :: hs

let test_case p (s:hsp) =
  let w = letify_hsp (add_case p (fst s) , snd s) in
  if is_trivial_hsp w then None else Some w

let tc = ref 0

let rec test_cases (s : hsp) = function
  | [] -> s
  | (p,_) :: tail ->
    Db.yield () ;
    match test_case p s , test_case (p_not p) s with
    | None , None -> incr tc ; [],F.p_true
    | Some w , None -> incr tc ; test_cases w tail
    | None , Some w -> incr tc ; test_cases w tail
    | Some _ , Some _ -> test_cases s tail

let rec collect_cond m = function
  | When _ | Have _ | Type _ | Init _ | Core _ | State _ -> ()
  | Branch(p,a,b) -> Letify.Split.add m p ; collect_seq m a ; collect_seq m b
  | Either cs -> List.iter (collect_seq m) cs

and collect_seq m seq = collect_steps m seq.seq_list
and collect_steps m steps = List.iter (fun s -> collect_cond m s.condition) steps

let pruning ?(solvers=[]) seq =
  if is_trivial seq then seq
  else
    begin
      let hs = (fst seq).seq_list in
      let p = snd seq in
      ignore solvers ;
      let m = Letify.Split.create () in
      collect_steps m hs ;
      tc := 0 ;
      let hsp = test_cases (hs,p) (Letify.Split.select m) in
      if !tc > 0 && Wp_parameters.has_dkey dkey_pruning then
        if is_trivial_hsp hsp then
          Wp_parameters.feedback "[Pruning] Trivial"
        else
          Wp_parameters.feedback "[Pruning] %d branche(s) removed" !tc ;
      let hs,p = hsp in
      sequence hs , p
    end

(* -------------------------------------------------------------------------- *)
(* --- Cleaning                                                           --- *)
(* -------------------------------------------------------------------------- *)

let rec collect_cond u = function
  | State _ -> ()
  | When p -> Cleaning.as_have u p
  | Have p -> Cleaning.as_have u p
  | Core p -> Cleaning.as_have u p
  | Type p -> Cleaning.as_type u p
  | Init p -> Cleaning.as_init u p
  | Branch(p,a,b) -> Cleaning.as_atom u p ; collect_seq u a ; collect_seq u b
  | Either cs -> List.iter (collect_seq u) cs
and collect_seq u seq = collect_steps u seq.seq_list
and collect_steps u steps =
  List.iter (fun s -> collect_cond u s.condition) steps

let rec clean_cond u = function
  | State _ as cond -> cond
  | When p -> When (Cleaning.filter_pred u p)
  | Have p -> Have (Cleaning.filter_pred u p)
  | Core p -> Core (Cleaning.filter_pred u p)
  | Type p -> Type (Cleaning.filter_pred u p)
  | Init p -> Init (Cleaning.filter_pred u p)
  | Branch(p,a,b) -> Branch(p,clean_seq u a,clean_seq u b)
  | Either cases -> Either(List.map (clean_seq u) cases)

and clean_seq u s =
  let s = clean_steps u s.seq_list in
  { seq_size = size_list s ;
    seq_vars = vars_list s ;
    seq_core = Pset.empty ;
    seq_catg = catg_list s ;
    seq_list = s }

and clean_steps u = function
  | [] -> []
  | s :: seq ->
    let c = clean_cond u s.condition in
    let seq = clean_steps u seq in
    match catg_cond c with
    | EMPTY -> seq
    | FALSE -> [update_cond s c]
    | TRUE | MAYBE -> update_cond s c :: seq

let clean (s,p) =
  let u = Cleaning.create () in
  Cleaning.as_atom u p ; collect_steps u s.seq_list ;
  sequence (clean_steps u s.seq_list) , p

(* -------------------------------------------------------------------------- *)
(* --- Filter Used Variables                                              --- *)
(* -------------------------------------------------------------------------- *)

module Filter =
struct

  module Gmap = Qed.Mergemap.Make(Fun)
  module Gset = Qed.Mergeset.Make(Fun)
  module Fset = Qed.Mergeset.Make(Field)

  module FP =
  struct
    type t = Gset.t * Fset.t
    let empty = Gset.empty , Fset.empty
    let union (a,u) (b,v) = Gset.union a b , Fset.union u v
    let subset (a,u) (b,v) = Gset.subset a b && Fset.subset u v
    let intersect (a,u) (b,v) = Gset.intersect a b || Fset.intersect u v
  end

  type used = {
    mutable fixpoint : bool ;
    mutable footprint : FP.t Tmap.t ; (* memoized by terms *)
    mutable footcalls : Fset.t Gmap.t ; (* memorized by function *)
    mutable gs : FP.t ; (* used in sequent *)
    mutable xs : Vars.t ;  (* used in sequent *)
  }

  [@@@ warning "-32"]
  let pp_gset fmt (u,v) =
    begin
      Format.fprintf fmt "@[<hov 2>{" ;
      Gset.iter (fun f -> Format.fprintf fmt "@ %a" Lang.Fun.pretty f) u ;
      Format.fprintf fmt "," ;
      Fset.iter (fun f -> Format.fprintf fmt "@ %a" Lang.Field.pretty f) v ;
      Format.fprintf fmt " }@]" ;
    end
  let pp_used fmt used =
    begin
      Format.fprintf fmt "@[<hov 2>{" ;
      Vars.iter (fun x -> Format.fprintf fmt "@ %a" Lang.F.Var.pretty x) used.xs ;
      Format.fprintf fmt "," ;
      Gset.iter (fun f -> Format.fprintf fmt "@ %a" Lang.Fun.pretty f) (fst used.gs) ;
      Format.fprintf fmt "," ;
      Fset.iter (fun f -> Format.fprintf fmt "@ %a" Lang.Field.pretty f) (snd used.gs) ;
      Format.fprintf fmt " }@]" ;
    end
  [@@@ warning "+32"]

  let fsetmap phi es =
    List.fold_left
      (fun fs e -> Fset.union fs (phi e))
      Fset.empty es

  let rec gvars_of_term ~deep m t =
    try Tmap.find t m.footprint
    with Not_found ->
      let collect_subterms acc =
        let gs = ref acc in
        let collect m gs e = gs := FP.union !gs (gvars_of_term ~deep m e) in
        F.lc_iter (collect m gs) t ;
        let s = !gs in
        m.footprint <- Tmap.add t s m.footprint ; s
      in
      match F.repr t with
      | Fun(f,[]) ->
        Gset.singleton f , Fset.empty
      | Fun(f,_) when not deep || is_coloring_lfun f ->
        Gset.empty , fset_of_lfun ~deep m f
      | Fun(f,_) ->
        collect_subterms (Gset.empty , fset_of_lfun ~deep m f)
      | Rget(_,fd) ->
        Gset.empty , Fset.singleton fd
      | Rdef fts ->
        Gset.empty ,
        List.fold_left (fun fs (f,_) -> Fset.add f fs)
          Fset.empty fts
      | _ ->
        collect_subterms FP.empty

  and gvars_of_pred m p = gvars_of_term m (F.e_prop p)

  and fset_of_tau (t : Lang.tau) =
    match t with
    | Qed.Logic.Array(ta,tb) ->
      Fset.union (fset_of_tau ta) (fset_of_tau tb)
    | Qed.Logic.Record fts ->
      fsetmap (fun (f,t) -> Fset.add f (fset_of_tau t)) fts
    | Qed.Logic.Data(adt,ts) ->
      Fset.union (fsetmap fset_of_tau ts) (fset_of_adt adt)
    | _ -> Fset.empty

  and fset_of_adt adt =
    fsetmap fset_of_field (Lang.fields_of_adt adt)

  and fset_of_field fd =
    let tf = Lang.tau_of_field fd in
    Fset.add fd (fset_of_tau tf)

  and fset_of_lemma ~deep m d =
    snd (gvars_of_pred ~deep m d.Definitions.l_lemma)

  and fset_of_var x = fset_of_tau (F.tau_of_var x)

  and fset_of_lfun ~deep m f =
    try Gmap.find f m.footcalls
    with Not_found ->
      (* bootstrap recursive calls *)
      m.footcalls <- Gmap.add f Fset.empty m.footcalls ;
      let fs =
        try
          let open Definitions in
          let d = Definitions.find_symbol f in
          let ds = fsetmap fset_of_var d.d_params in
          let df =
            match d.d_definition with
            | Logic _ -> Fset.empty
            | Function(_,_,t) -> snd (gvars_of_term ~deep m t)
            | Predicate(_,p) -> snd (gvars_of_pred ~deep m p)
            | Inductive ds -> fsetmap (fset_of_lemma ~deep m) ds
          in Fset.union ds df
        with Not_found ->
          Fset.empty
      in m.footcalls <- Gmap.add f fs m.footcalls ; fs

  let collect_have m p =
    begin
      m.gs <- FP.union m.gs (gvars_of_pred ~deep:true m p) ;
      m.xs <- Vars.union m.xs (F.varsp p) ;
    end

  let rec collect_condition m = function
    | Have p | When p | Core p -> collect_have m p
    | Type _ | Init _ | State _ -> ()
    | Branch(p,sa,sb) -> collect_have m p ; collect_seq m sa ; collect_seq m sb
    | Either cs -> List.iter (collect_seq m) cs

  and collect_step m s = collect_condition m s.condition
  and collect_seq m s = List.iter (collect_step m) s.seq_list

  let rec filter_pred m p =
    match F.p_expr p with
    | And ps -> F.p_all (filter_pred m) ps
    | _ ->
      if Vars.subset (F.varsp p) m.xs then
        begin
          let gs = gvars_of_pred ~deep:false m p in
          if FP.subset gs m.gs then p else
          if FP.intersect gs m.gs then
            (m.fixpoint <- false ; m.gs <- FP.union gs m.gs ; p)
          else p_true
        end
      else p_true

  let rec filter_steplist m = function
    | [] -> []
    | s :: w ->
      match s.condition with
      | State _ | Have _ | When _ | Core _ | Branch _ | Either _ ->
        s :: filter_steplist m w
      | Type p ->
        let p = filter_pred m p in
        let w = filter_steplist m w in
        if p != F.p_true then
          let s = update_cond s (Type p) in
          s :: w
        else w
      | Init p ->
        let p = filter_pred m p in
        let w = filter_steplist m w in
        if p != F.p_true then
          let s = update_cond s (Init p) in
          s :: w
        else w

  let make (seq,g) =
    let m = {
      gs = FP.empty ;
      xs = Vars.empty ;
      fixpoint = false ;
      footprint = Tmap.empty ;
      footcalls = Gmap.empty ;
    } in
    List.iter (collect_step m) seq.seq_list ; collect_have m g ;
    Kernel.debug ~level:3 "Collected %a" pp_used m ;
    let rec loop () =
      m.fixpoint <- true ;
      let hs' = filter_steplist m seq.seq_list in
      if m.fixpoint then ( sequence hs' , g ) else loop ()
    in loop ()

end

let filter = Filter.make

(* -------------------------------------------------------------------------- *)
(* --- Filter Parasite Definitions                                        --- *)
(* -------------------------------------------------------------------------- *)

module Parasite =
struct

  open Qed.Logic

  type usage = Used | Def of F.term
  type domain = usage Vmap.t

  [@@@ warning "-32"]
  let pretty fmt w =
    Format.fprintf fmt "@[<hov 2>{" ;
    Vmap.iter
      (fun x u -> match u with
         | Used -> Format.fprintf fmt "@ %a" F.pp_var x
         | Def e -> Format.fprintf fmt "@ @[<hov 2>%a:=%a;@]" F.pp_var x F.pp_term e
      ) w ;
    Format.fprintf fmt " }@]"
  [@@@ warning "+32"]

  let cyclic w x e =
    let m = ref Vars.empty in
    let once x = if Vars.mem x !m then false else (m := Vars.add x !m ; true) in
    let rec walk_y w x y =
      if F.Var.equal x y then raise Exit ;
      if once x then
        let r = try Vmap.find x w with Not_found -> Used in
        match r with Used -> () | Def e -> walk_e w x e
    and walk_e w x e = Vars.iter (walk_y w x) (F.vars e) in
    try walk_e w x e ; false with Exit -> true

(*
  let pivots w a b =
    let rec collect xs e =
      match F.repr e with
      | Fvar x -> x :: xs
      | Add es -> List.fold_left collect xs es
      | _ -> xs in
    let define w a b =
      let xs = collect [] a in
      let def r x = x , F.e_sub r (F.e_var x) in
      let filter w (x,e) = acyclic w x e in
      if xs = [] then [] else
        List.filter (filter w)
          (List.map (def (F.e_sub b a)) xs) in
    define w a b @ define w b a
*)

  let rec add_used (w : domain) xs = Vars.fold add_usedvar xs w
  and add_usedvar x w =
    try match Vmap.find x w with
      | Used -> w
      | Def e -> add_used (Vmap.add x Used w) (F.vars e)
    with Not_found -> Vmap.add x Used w

  let add_def (w : domain) x e =
    try
      let xs = F.vars e in
      if cyclic w x e then add_used (add_usedvar x w) xs
      else
        match Vmap.find x w with
        | Used -> add_used w xs
        | Def e0 -> if F.equal e0 e then w else add_used (Vmap.add x Used w) xs
    with Not_found -> Vmap.add x (Def e) w

  let kind x w =
    try Some (Vmap.find x w)
    with Not_found -> None

  let add_eq (w : domain) x y =
    match kind x w , kind y w with
    | None , None ->
      let cmp = F.Var.compare x y in
      if cmp > 0 then add_def w x (F.e_var y) else
      if cmp < 0 then add_def w y (F.e_var x) else
        w
    | None , Some Used -> add_def w x (F.e_var y)
    | Some Used , None -> add_def w y (F.e_var x)
    | Some(Def e),(None | Some Used)
    | (None|Some Used),Some (Def e)
      -> add_usedvar x (add_usedvar y (add_used w (F.vars e)))
    | Some Used,Some Used -> w
    | Some(Def a),Some(Def b) ->
      let xs = Vars.union (F.vars a) (F.vars b) in
      add_usedvar x (add_usedvar y (add_used w xs))

  let branch p wa wb =
    let pool = ref (F.varsp p) in
    let w0 = Vmap.union
        (fun _x u v ->
           match u,v with
           | Used,Used -> Used
           | Def a,Def b -> Def( F.e_if (F.e_prop p) a b )
           | Def e,Used | Used,Def e ->
             pool := Vars.union !pool (F.vars e) ; Used
        ) wa wb in
    add_used w0 !pool

  let rec usage w p =
    match F.repr p with
    | And ps -> List.fold_left usage w ps
    | Eq(a,b) ->
      begin match F.repr a , F.repr b with
        | Fvar x , Fvar y -> add_eq w x y
        | Fvar x , _ -> add_def w x b
        | _ , Fvar y -> add_def w y a
        | _ -> add_used w (F.vars p)
      end
    | _ -> add_used w (F.vars p)

  let rec collect_step w s =
    match s.condition with
    | Type _ | State _ -> w
    | Have p | Core p | Init p | When p -> usage w (F.e_prop p)
    | Branch(p,a,b) ->
      let wa = collect_seq w a in
      let wb = collect_seq w b in
      branch p wa wb
    | Either ws ->
      List.fold_left collect_seq w ws

  and collect_seq w s = List.fold_left collect_step w s.seq_list

  let parasites w =
    Vmap.fold
      (fun x u xs -> match u with Used -> xs | Def _ -> Vars.add x xs)
      w Vars.empty

  let rec filter xs p =
    match F.p_expr p with
    | And ps -> p_all (filter xs) ps
    | _ -> if Vars.intersect (F.varsp p) xs then F.p_true else p

  let filter (hs,g) =
    let w = collect_seq (add_used Vmap.empty (F.varsp g)) hs in
    let xs = parasites w in
    if Vars.is_empty xs then (hs,g)
    else map_sequence (filter xs) hs , g

end

let parasite = Parasite.filter

module InitFilter =
struct
  let add_state filter s = s :: filter

  let rec collect_step filter s =
    match s.condition with
    | State s -> add_state filter s
    | Have _ | Core _ | Init _ | When _ | Type _ -> filter
    | Branch(_p,a,b) -> collect_seq (collect_seq filter a) b
    | Either ws -> List.fold_left collect_seq filter ws

  and collect_seq filter s =
    List.fold_left collect_step filter s.seq_list

  exception Found

  let has_init filter pred =
    let visited = ref Tset.empty in
    let rec term_is_init_in_states states t =
      let raise_if_is_init t s =
        if Lang.F.is_primitive t then ()
        else
          match Mstate.lookup s t with
          | Mlval(_, KInit) | Mchunk(_, KInit) -> raise Found
          | _ -> ()
      in
      if Tset.mem t !visited then ()
      else begin
        visited := Tset.add t !visited ;
        List.iter (raise_if_is_init t) states ;
        Lang.F.lc_iter (term_is_init_in_states states) t
      end
    in
    try term_is_init_in_states filter (F.e_prop pred) ; false
    with Found -> true

  let rec do_filter filter p =
    let on_sub_nodes = do_filter filter in
    match F.p_expr p with
    | And ps -> p_all on_sub_nodes ps
    | If(e,a,b) -> F.p_if e (on_sub_nodes a) (on_sub_nodes b)
    | _ when has_init filter p -> p_true
    | _ -> p

  let filter (hs, g) =
    let filter = collect_seq [] hs in
    if has_init filter g then (hs, g)
    else map_sequence (do_filter filter) hs, g
end

let init_filter = InitFilter.filter

(* -------------------------------------------------------------------------- *)
(* --- Finalization                                                       --- *)
(* -------------------------------------------------------------------------- *)

let close_cond = function
  | Type _ when Wp_parameters.SimplifyType.get () -> p_true
  | c -> pred_cond c

let closure = ref []
let at_closure f = closure := f::!closure
let alter_closure sequent = List.fold_left (fun seq f -> f seq) sequent !closure

let hyps s = List.map (fun s -> close_cond s.condition) s.seq_list
let head s =
  match s.condition with
  | Have p | When p | Core p | Init p | Type p
  | Branch(p,_,_) -> p
  | Either _ | State _ -> p_true
let have s =
  match s.condition with
  | Have p | When p | Core p | Init p | Type p -> p
  | Branch _ | Either _ | State _ -> p_true

let condition s = F.p_conj (hyps s)
let close sequent =
  let s,goal = alter_closure sequent in
  F.p_close (F.p_hyps (hyps s) goal)

(* -------------------------------------------------------------------------- *)
(* --- Visitor                                                            --- *)
(* -------------------------------------------------------------------------- *)

let list seq = seq.seq_list
let iter f seq = List.iter f seq.seq_list

(* -------------------------------------------------------------------------- *)
(* --- Index                                                              --- *)
(* -------------------------------------------------------------------------- *)

let rec index_list k = function
  | [] -> k
  | s::w -> index_list (index_step k s) w

and index_step k s =
  s.id <- k ; let k = succ k in
  match s.condition with
  | Have _ | When _ | Type _ | Core _ | Init _ | State _ -> k
  | Branch(_,a,b) -> index_list (index_list k a.seq_list) b.seq_list
  | Either cs -> index_case k cs

and index_case k = function
  | [] -> k
  | c::cs -> index_case (index_list k c.seq_list) cs

let steps seq = index_list 0 seq.seq_list

let index (seq,_) = ignore (steps seq)

(* -------------------------------------------------------------------------- *)
(* --- Access                                                             --- *)
(* -------------------------------------------------------------------------- *)

let rec at_list k = function
  | [] -> assert false
  | s::w ->
    if k = 0 then s else
      let n = s.size in
      if k < n then at_step (k-1) s.condition else at_list (k - n) w

and at_step k = function
  | Have _ | When _ | Type _ | Core _ | Init _ | State _ -> assert false
  | Branch(_,a,b) ->
    let n = a.seq_size in
    if k < n then
      at_list k a.seq_list
    else
      at_list (k-n) b.seq_list
  | Either cs -> at_case k cs

and at_case k = function
  | [] -> assert false
  | c::cs ->
    let n = c.seq_size in
    if k < n then at_list k c.seq_list else at_case (k - n) cs

let step_at seq k =
  if 0 <= k && k < seq.seq_size
  then at_list k seq.seq_list
  else raise Not_found

(* -------------------------------------------------------------------------- *)
(* --- Insertion                                                          --- *)
(* -------------------------------------------------------------------------- *)

let in_sequence_add_list ~replace =
  let rec in_list k h w =
    if k = 0 then
      List.rev_append (List.rev h)
        (if replace
         then match w with
           | [] -> assert false
           | _::w -> w
         else w)
    else
      match w with
      | [] -> assert false
      | s::w ->
        let n = s.size in
        if k < n then
          let cond = in_step (k-1) h s.condition in
          update_cond s cond :: w
        else s :: in_list (k-n) h w

  and in_step k h = function
    | Have _ | When _ | Type _ | Core _ | Init _ | State _ -> assert false
    | Branch(p,a,b) ->
      let n = a.seq_size in
      if k < n then
        Branch(p,in_sequence_add_list k h a,b)
      else
        Branch(p,a,in_sequence_add_list (k-n) h b)
    | Either cs -> Either (in_case k h cs)

  and in_case k h = function
    | [] -> assert false
    | c::cs ->
      let n = c.seq_size in
      if k < n
      then in_sequence_add_list k h c :: cs
      else c :: in_case (k-n) h cs

  and in_sequence_add_list k h s = sequence (in_list k h s.seq_list)
  in in_sequence_add_list

let in_sequence ~replace id h = in_sequence_add_list ~replace id [h]

let size seq = seq.seq_size

let insert ?at step sequent =
  let seq,goal = sequent in
  let at = match at with None -> seq.seq_size | Some k -> k in
  if 0 <= at && at <= seq.seq_size
  then in_sequence ~replace:false at step seq , goal
  else raise Not_found

let replace ~at step sequent =
  let seq,goal = sequent in
  if 0 <= at && at <= seq.seq_size
  then in_sequence ~replace:true at step seq , goal
  else raise Not_found

let replace_by_step_list ~at step_list sequent =
  let seq,goal = sequent in
  if 0 <= at && at <= seq.seq_size
  then in_sequence_add_list ~replace:true at step_list seq , goal
  else raise Not_found

(* -------------------------------------------------------------------------- *)
(* --- Replace                                                            --- *)
(* -------------------------------------------------------------------------- *)

let subst f s = map_sequent (Lang.p_subst f) s

(* -------------------------------------------------------------------------- *)