(*
This file is part of the dpll(t) engine sammy
Copyright (C) 2004  The University of Iowa
Mike Decoster

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*)

(* skolemizer.ml *)

open Global

open Formula

type instantiation_return_type = 
       FINISH
     | EXHAUSTED_INSTANTIATIONS
     | INSTANTIATIONS of Pred.pred list * Global.pred_id list list

let cstore=Sys_state.cstore
let theory=cstore#theory

let contains_var = Formula.contains_var
let remove_var = Formula.remove_var

exception Dontknow
exception Expression_must_be_quantified

exception Quantifier_must_have_binders

exception Var_instantiated_as_var

exception Skolemizer_already_initialized

class skolemizer ((pred_db,term_db) : Pred_db.pred_db * Term_db.term_db) =
let bool_type_id=theory#boolType in
let quantified_abstraction_table=
      (Hashtbl.create 200 : (Global.pred_id, exprtype*int*int) Hashtbl.t)
in
let max_instances_per_call = ref Global.pars.max_instances in
let max_quantifier_expansion_depth = ref 1 in
let deepest_quantifier_seen = ref 0 in
let done_instantiating=ref false in
let add_quantified_abstraction pred_id expr level depth= 
     if level> !deepest_quantifier_seen then 
       deepest_quantifier_seen:=level;
     if Global.debug then Printf.printf "\nadded pred_id %d to the quantified abstraction table\n" pred_id;
     Hashtbl.add quantified_abstraction_table pred_id (expr,level,depth) 
in
let instantiations = ref 0 in
(*let increase_instantiation_total() = instantiations:=(!instantiations+1) in*)
let increase_instantiation_total() = () in
let lookup_quantified_abstraction = Hashtbl.find quantified_abstraction_table in
let positive_instantiation_table = (Hashtbl.create 200: (Global.pred_id, int) Hashtbl.t) in
let instantiated_pos pred_id = (*false*)
    (try 
       if (Hashtbl.find positive_instantiation_table pred_id)>1 then
         true 
       else
         false
     with Not_found-> false
    ) 
in
let set_instantiated_positively pred_id = 
    (try 
       let num_times_instantiated=(Hashtbl.find positive_instantiation_table pred_id) in
         Hashtbl.replace positive_instantiation_table pred_id (num_times_instantiated+1)
     with Not_found-> 
         Hashtbl.add positive_instantiation_table pred_id 1
    ) 
in
let negative_instantiation_table = (Hashtbl.create 200: (Global.pred_id, int) Hashtbl.t) in
let instantiated_neg pred_id = (*false*)
    (try 
       if (Hashtbl.find negative_instantiation_table pred_id)>1 then
         true 
       else
         false
     with Not_found-> false
    ) 
in
let set_instantiated_negatively pred_id = 
    (try 
       let num_times_instantiated=(Hashtbl.find negative_instantiation_table pred_id) in
         Hashtbl.replace negative_instantiation_table pred_id (num_times_instantiated+1)
     with Not_found-> 
         Hashtbl.add negative_instantiation_table pred_id 1
    ) 
in
let add_pred = pred_db#add_pred in
let add_abs_pred = pred_db#add_uninterpreted_abstract_pred in
let add_quant_abs_pred = pred_db#add_quantified_abstraction_pred in
let make_pred sym terms =
  let new_id = theory#appFunction
    (theory#decFunction sym (theory#get_function_type sym)) (term_ids terms)
  in
(* if we can guarantee the function was declared in parsing, use: *)
(*  let new_id = theory#appFunction (theory#get_id sym) (term_ids terms) in *)
  let temp = expr_to_string (FUN(sym,terms)) in
    add_pred new_id temp;
    new_id
in
let make_uninterpreted_pred () =
  let new_id = theory#next_id in
    add_abs_pred new_id;
    new_id
in
(* makes an uninterpreted, abstract pred, returns its id *)
(* note the unit parameter *)
let make_quant_abs_pred () =
  let new_id = theory#next_id in
    add_quant_abs_pred new_id;
    new_id
in
(* marks an existing predicate as interpreted (safe to do after construction) *)
(* also declares it to the theory (in case it wasn't previously) *)
let mark_interpreted (pid:Global.pred_id) =
  let pred = pred_db#get_pred pid in
  theory#decOldPred (Pred.formula pred) pid;
  Pred.set_interpreted pred true
in
let make_rel_pred (rel) ((E(Some e1,_,_,_,_)) as p1) ((E(Some e2,_,_,_,_)) as p2) =
  let (new_id,relstr) = match rel with
      GT -> (theory#create_greater_than e1 e2),">"
    | GTE -> (theory#create_greater_than_or_equals e1 e2),">="
    | LT -> (theory#create_less_than e1 e2),"<"
    | LTE -> (theory#create_less_than_or_equals e1 e2),"=<"
    | EQ -> (theory#create_equals e1 e2),"="
    | NEQ -> (theory#create_equals e1 e2),"!="
  in
  let temp = expr_to_string (REL(rel,p1,p2)) in
  add_pred new_id temp;
  match rel with
      NEQ -> Global.compl new_id
    | _ -> new_id
in
let make_binop (BINOP(op,E(Some id1,_,_,_,_),E(Some id2,_,_,_,_)) as expr) =
      let id=match op with
              PLUS ->theory#appPlus id1 id2
            | MINUS->theory#appMinus id1 id2
            | MULT->theory#appMult id1 id2
            | DIV->theory#appDiv id1 id2
            | INTDIV->theory#appIntDiv id1 id2
            | MOD->theory#appMod id1 id2
            | POW->theory#appPow id1 id2
      in
      id
in
let make_isint ((E(Some id1,_,_,_,_)) as expr)=
  let id=theory#decl_interpreted_function "ISINT" [Global.id_string id1] in
  let 
    temp=exprtype_to_string (E(Some id,empty_expression_variables,bool_type_id,PRED("ISINT",[expr]),Escjava_label.empty_label))
  in
    add_pred id temp;
    id
in
let make_uminus (UMINUS(E(Some id1,_,_,_,_)) as expr)=
  let id=theory#appUnaryMinus id1 in
    id
in
let make_floor (E(Some id1,_,_,_,_) as expr)=
  let id=theory#decl_interpreted_function "FLOOR" [Global.id_string id1] in
    id
in
(* returns true if the freelist has a variable in common with the instantiation
list*)
let instlist_has_common_var_with_freelist freelist instl =
      List.exists (function (var_name,_)->contains_var var_name freelist) instl
in
(* returns freelist with each variable in the instantiation list removed *)
let freelist_without_instantiated_vars freelist instl =
  List.fold_left (fun freelist bind -> let (var_name,_)=bind in
                                        remove_var var_name freelist
                 ) freelist instl
in
let remove_binder_from_instl instl (_,var_name,_)=
    if(List.mem_assoc var_name instl) then
      List.remove_assoc var_name instl
    else
      instl
in
(* takes expression id to be skolemized & makes a new variable of the type *)
(* returns new expression ID *)
(* name : base string name for the expression(may be empty) *)
(* typeid : id of type of expression to be replaced *)
let define_fresh_constant var_id sort_id=
  let nextid = theory#next_id in
  let idstr = (string_of_int var_id)^"?"^(string_of_int nextid) in
  let new_id = theory#decVariable idstr sort_id in
  term_db#add_term sort_id new_id;
  E(Some new_id,empty_expression_variables,sort_id,ID(new_id,idstr,[]),Escjava_label.empty_label)
in
let get_expr_to_instantiate_var sort_id var_id =
    try
      let random_term_id=term_db#get_random_term sort_id in
      let id_name = "i"^(string_of_int random_term_id)^"?" in
        E(Some random_term_id,empty_expression_variables,sort_id,ID(random_term_id,id_name,[]),Escjava_label.empty_label)
     with Term_db.Empty_term_database -> define_fresh_constant var_id sort_id
in
let rec instantiate_expression (new_preds : Pred.pred list) instl
                               (E(id,expr_vars,sort_id,expr2,label) as expr)
                               =
   (* remove any variables from the expression variables structure that were in the instantiation list *)
   let updated_freelist=freelist_without_instantiated_vars expr_vars.freevars instl in
   let updated_exist_list=freelist_without_instantiated_vars expr_vars.existential_bound_vars instl in
   let updated_universal_list=freelist_without_instantiated_vars expr_vars.universal_bound_vars instl in
   let updated_expr_vars=Formula.new_expr_vars updated_freelist updated_exist_list updated_universal_list in
      begin (* expr2 match *)
      match expr2 with
        NOT(E(id2,expr_vars2,_,e1,label1))->
            let expr_to_instantiate= E(id2,expr_vars,sort_id,e1,label1) in
              let (new_preds',instantiated_expr)=
                instantiate_expression new_preds instl expr_to_instantiate
              in
              begin
              match id,updated_freelist with
                (None,[])->
                  let newpred_id=make_uninterpreted_pred() in
                  let newpred = pred_db#get_pred newpred_id in
                  let new_pred_list =
                      if Pred.pred_status newpred   =   Undet then
                        newpred::new_preds'
                      else
                        new_preds'
                  in
                  (new_pred_list,E(Some newpred_id,updated_expr_vars,bool_type_id, NOT(instantiated_expr),label))
              | _ ->
                  (new_preds',E(id,updated_expr_vars,bool_type_id, NOT(instantiated_expr),label))
              end
      | OR(el)->
            let (new_preds',instantiated_expr_list)=
                List.fold_left (
                    fun ((preds,el) as acc) (E(_,{freevars=freevars2},_,_,_) as e1) ->
                       if(instlist_has_common_var_with_freelist freevars2 instl)
                       then
                         let (preds',instantiated_expr)=
                           instantiate_expression preds instl e1
                         in
                         (preds',(instantiated_expr::el))
                       else
                         (preds,(e1::el))
                               ) (new_preds,[]) el
            in
            begin
            match id,updated_freelist with
              (None,[])->
                let newpred_id=make_uninterpreted_pred() in
                let newpred = pred_db#get_pred newpred_id in
                let new_pred_list =
                    if Pred.pred_status newpred   =   Undet then
                      newpred::new_preds'
                    else
                      new_preds'
                in
                (new_pred_list,E(Some newpred_id,updated_expr_vars,bool_type_id,OR(instantiated_expr_list),label))
            | _ ->
                (new_preds',E(id,updated_expr_vars,bool_type_id,OR(instantiated_expr_list),label))
            end
      | AND(el)->
          let (new_preds',instantiated_expr_list)=
              List.fold_left (
                  fun ((preds,el) as acc) (E(_,{freevars=freevars2},_,_,_) as e1) ->
                     if(instlist_has_common_var_with_freelist freevars2 instl)
                     then
                       let (preds',instantiated_expr)=
                         instantiate_expression preds instl e1
                       in
                       (preds',(instantiated_expr::el))
                     else
                       (preds,(e1::el))
                             ) (new_preds,[]) el
          in
          begin
          match id,updated_freelist with
            (None,[])->
              let newpred_id=make_uninterpreted_pred() in
              let newpred = pred_db#get_pred newpred_id in
              let new_pred_list =
                  if Pred.pred_status newpred   =   Undet then
                    newpred::new_preds'
                  else
                    new_preds'
              in
              (new_pred_list,E(Some newpred_id,updated_expr_vars,bool_type_id,AND(instantiated_expr_list),label))
          | _ ->
              (new_preds',E(id,updated_expr_vars,bool_type_id,AND(instantiated_expr_list),label))
          end
      | IFF( (E(id1,{freevars=freevars1},sort_id1,subexpr1,label1) as e1),(E(id2,{freevars=freevars2},sort_id2,subexpr2,label2) as e2))->
          let (new_preds',instantiated_expr1)=
              if(instlist_has_common_var_with_freelist freevars1 instl) then
                instantiate_expression new_preds instl e1
              else
                (new_preds,e1)
          in
          let (new_preds'',instantiated_expr2)=
              if(instlist_has_common_var_with_freelist freevars2 instl) then
                instantiate_expression new_preds' instl e2
              else
                (new_preds',e2)
          in
          begin
          match id,updated_freelist with
            (None,[])->
              let newpred_id=make_uninterpreted_pred() in
              let newpred = pred_db#get_pred newpred_id in
              let new_pred_list =
                  if Pred.pred_status newpred   =   Undet then
                    newpred::new_preds''
                  else
                    new_preds''
              in
              (new_pred_list,E(Some newpred_id,updated_expr_vars,bool_type_id,
                              IFF(instantiated_expr1,instantiated_expr2),label))
          | _ ->
              (new_preds'',E(id,updated_expr_vars,bool_type_id,
                              IFF(instantiated_expr1,instantiated_expr2),label))
          end
      | QUANT(bindl, e1)->
          let (new_preds',instantiated_expr)=
              instantiate_expression new_preds instl e1
          in
          begin
            let updated_bindl=List.fold_left (fun bindl (var_name,_) -> remove_var_from_binder_list var_name bindl ) bindl instl in
            (* if all the binders have been accounted by the instantiation list, remove the quantifier *)
            if updated_bindl=[] then
              (new_preds',instantiated_expr)
            else
            begin
              match id,updated_freelist with
                (None,[])->
                  let newpred_id=make_quant_abs_pred() in
                  let newpred = pred_db#get_pred newpred_id in
                  let new_pred_list =
                      if Pred.pred_status newpred   =   Undet then
                        newpred::new_preds'
                      else
                        new_preds'
                  in
                  (new_pred_list,E(Some newpred_id,updated_expr_vars,bool_type_id,
                                    QUANT(updated_bindl,instantiated_expr),Escjava_label.empty_label))
              | _ ->
                  (new_preds',E(id,updated_expr_vars,bool_type_id,QUANT(updated_bindl,instantiated_expr),Escjava_label.empty_label))
            end
          end
      | PRED(name,el) ->
          let (new_preds',rev_instantiated_expr_list)=
              List.fold_left ( fun acc expr->
                                let (preds,exprl)=acc in
                                let (new_preds,iexpr)=
                                    instantiate_expression preds instl expr
                                in
                                (new_preds,iexpr::exprl)
                             ) (new_preds,[]) el
          in
          let instantiated_expr_list=List.rev rev_instantiated_expr_list in
          begin
            match name,instantiated_expr_list with
            | ("ISINT",[expr1]) ->
              let (new_preds',instantiated_expr1)=
                  instantiate_expression new_preds instl expr1
              in
              if updated_freelist  =  [] then
                let newpred_id= make_isint (instantiated_expr1) in
                let newpred = pred_db#get_pred newpred_id in
                let new_pred_list =
                    if Pred.pred_status newpred   =   Undet then
                      newpred::new_preds'
                    else
                      new_preds'
                in
                (new_pred_list, E(Some newpred_id,updated_expr_vars, bool_type_id,
                  PRED("ISINT",[instantiated_expr1]),label))
              else
                (new_preds', E(None,updated_expr_vars, bool_type_id,
                  PRED("ISINT",[instantiated_expr1]),label))
            | _ ->
              if updated_freelist  =  [] then
                let newpred_id=make_pred name instantiated_expr_list in
                let newpred = pred_db#get_pred newpred_id in
                let new_pred_list =
                    if Pred.pred_status newpred   =   Undet then
                      newpred::new_preds'
                    else
                      new_preds'
                in
                (new_pred_list,E(Some newpred_id,updated_expr_vars, bool_type_id,
                                  PRED(name, instantiated_expr_list),label))
              else
                (new_preds',
                  E(None,updated_expr_vars,bool_type_id,
                    PRED(name,instantiated_expr_list),label))
            end
      | REL(symbol,expr1,expr2) ->
          let (new_preds',instantiated_expr1)=
              instantiate_expression new_preds instl expr1
          in
          let (new_preds'',instantiated_expr2)=
              instantiate_expression new_preds' instl expr2
          in
          if updated_freelist  =  [] then
            let newpred_id=
                make_rel_pred symbol instantiated_expr1 instantiated_expr2
            in
            let newpred = pred_db#get_pred (Global.unsigned newpred_id) in
            let new_pred_list =
                if Pred.pred_status newpred   =   Undet then
                  newpred::new_preds''
                else
                  new_preds''
            in
            (new_pred_list,
              E(Some newpred_id,updated_expr_vars,bool_type_id,
                REL(symbol,instantiated_expr1,instantiated_expr2),label))
          else
            (new_preds'',
              E(None,updated_expr_vars,bool_type_id,
                REL(symbol,instantiated_expr1,instantiated_expr2),label))
      | FUN(name,el) ->
          let (new_preds',rev_instantiated_expr_list)=
              List.fold_left ( fun acc expr->
                                let (preds,exprl)=acc in
                                let (new_preds,iexpr)=
                                    instantiate_expression preds instl expr
                                in
                                (new_preds,iexpr::exprl)
                             ) (new_preds,[]) el
          in
          let instantiated_expr_list=List.rev rev_instantiated_expr_list in
          begin
          match name,instantiated_expr_list with
            ("ARRAY_LOOKUP",[instantiated_expr1;instantiated_expr2])->
              if updated_freelist=[] then
                let E(Some id1,{freevars=[]},_,_,_)=instantiated_expr1 in
                let E(Some id2,{freevars=[]},_,_,_)=instantiated_expr2 in
                let new_id=
(**)              theory#decl_interpreted_function 
(**)              "ARRAY_LOOKUP" [Global.id_string id1;Global.id_string id2]
                in
                let name="("^(exprtype_to_string instantiated_expr1)^"["
                                  ^(exprtype_to_string instantiated_expr2)^"])"
                in
                let new_pred_list=
                      if sort_id  =  theory#boolType then
                      begin
                        add_pred new_id name;
                        let newpred = pred_db#get_pred new_id in
                          if Pred.pred_status newpred   =   Undet then
                            newpred::new_preds'
                          else
                            new_preds'
                      end
                      else
                      begin
                        term_db#add_term sort_id new_id;
                        new_preds'
                      end
                in
                let accumulated_labels=gather_labels_from_exprlist instantiated_expr_list in
                (new_pred_list,
                    E(Some(new_id),updated_expr_vars, sort_id,
                        ID(new_id,name,accumulated_labels),label))
              else
                (new_preds',
                  E(None,updated_expr_vars, sort_id,
                    FUN("ARRAY_LOOKUP",[instantiated_expr1;instantiated_expr2]),label))
          | ("TUPLE_LOOKUP",[instantiated_expr1;E(_,_,_,INT(num),_)])->
            if updated_freelist=[] then
              let E(Some id1,{freevars=[]},_,_,_)=instantiated_expr1 in
              let new_id=
(**)              theory#decl_interpreted_function 
(**)                "TUPLE_LOOKUP" [Global.id_string id1;string_of_int num]
              in
              let name="("^(exprtype_to_string instantiated_expr1)^"."
                                ^(string_of_int num)^")"
              in
              let new_pred_list=
                    if sort_id  =  theory#boolType then
                    begin
                      add_pred new_id name;
                      let newpred = pred_db#get_pred new_id in
                        if Pred.pred_status newpred   =   Undet then
                          newpred::new_preds'
                        else
                          new_preds'
                    end
                    else
                    begin
                      term_db#add_term sort_id new_id;
                      new_preds'
                    end
              in
              let accumulated_labels=gather_labels_from_exprlist instantiated_expr_list in
              (new_pred_list,
                  E(Some(new_id),updated_expr_vars, sort_id,
                      ID(new_id,name,accumulated_labels),label))
            else
              (new_preds',
                E(None,updated_expr_vars, sort_id,
                  FUN("TUPLE_LOOKUP",[instantiated_expr1;int_expr(num)]),label))
          | ("RECORD_LOOKUP",[instantiated_expr1;E(_,_,_,STRING(field),_)]) ->
            if updated_freelist=[] then
              let E(Some id1,{freevars=[]},_,_,_)=instantiated_expr1 in
              let new_id=
(**)              theory#decl_interpreted_function 
(**)                "RECORD_LOOKUP" [Global.id_string id1;field]
              in
              let name="("^(exprtype_to_string instantiated_expr1)^"." ^field^")" in
              let new_pred_list=
                    if sort_id  =  theory#boolType then
                    begin
                      add_pred new_id name;
                      let newpred = pred_db#get_pred new_id in
                        if Pred.pred_status newpred   =   Undet then
                          newpred::new_preds'
                        else
                          new_preds'

                    end
                    else
                    begin
                      term_db#add_term sort_id new_id;
                      new_preds'
                    end
              in
              let accumulated_labels=gather_labels_from_exprlist instantiated_expr_list in
              (new_pred_list,
                  E(Some(new_id),updated_expr_vars, sort_id,
                      ID(new_id,name,accumulated_labels),label))
            else
              (new_preds',
                E(None,updated_expr_vars, sort_id,
                  FUN("RECORD_LOOKUP",[instantiated_expr1;string_expr(field)]),label))
          | ("ARRAY_WRITE",[instantiated_expr1;instantiated_expr2;instantiated_expr3]) ->
            if updated_freelist  =  [] then
              let E(Some id1,{freevars=[]},_,_,_)=instantiated_expr1 in
              let E(Some id2,{freevars=[]},_,_,_)=instantiated_expr2 in
              let E(Some id3,{freevars=[]},_,_,_)=instantiated_expr3 in
              let new_id=
(**)              theory#decl_interpreted_function 
(**)                "ARRAY_WRITE" [Global.id_string id1;Global.id_string id2;Global.id_string id3]
              in
              let accumulated_labels=gather_labels_from_exprlist instantiated_expr_list in
              term_db#add_term sort_id new_id;
              (new_preds',
                  E(Some(new_id),updated_expr_vars, sort_id,
                      ID(new_id,"("^(exprtype_to_string instantiated_expr1)^" WITH "
                                  ^(exprtype_to_string instantiated_expr2) ^" := "
                                  ^(exprtype_to_string instantiated_expr3)^")",accumulated_labels),label))
            else
              (new_preds',
                E(None,updated_expr_vars, sort_id,
                  FUN("ARRAY_WRITE",[instantiated_expr1;instantiated_expr2;instantiated_expr3]),label)) 
          | ("RECORD_WRITE",[instantiated_expr1;E(_,_,_,STRING(field),_);instantiated_expr2]) ->
            if updated_freelist = [] then
              let E(Some id1,{freevars=[]},_,_,_)=instantiated_expr1 in
              let E(Some id2,{freevars=[]},_,_,_)=instantiated_expr2 in
              let new_id=
(**)              theory#decl_interpreted_function 
(**)                "RECORD_WRITE" [Global.id_string id1;field;Global.id_string id2]
              in
              term_db#add_term sort_id new_id;
              let accumulated_labels=gather_labels_from_exprlist instantiated_expr_list in
              (new_preds',
                  E(Some(new_id),updated_expr_vars, sort_id,
                      ID(new_id,"("^(exprtype_to_string instantiated_expr1)^" WITH ."
                                  ^ field ^" := "
                                  ^(exprtype_to_string instantiated_expr2)^")",accumulated_labels),label))
            else
              (new_preds',
                E(None,updated_expr_vars, sort_id,
                  FUN("RECORD_WRITE",[instantiated_expr1;string_expr(field);instantiated_expr2]),label)) 

          | ("TUPLE_WRITE",[instantiated_expr1;E(_,_,_,INT(num),_);instantiated_expr2]) ->
            if updated_freelist  =  [] then
              let E(Some id1,{freevars=[]},_,_,_)=instantiated_expr1 in
              let E(Some id2,{freevars=[]},_,_,_)=instantiated_expr2 in
              let new_id=
(**)              theory#decl_interpreted_function 
(**)               "TUPLE_WRITE" [Global.id_string id1;string_of_int num;Global.id_string id2]
              in
              term_db#add_term sort_id new_id;
              let accumulated_labels=gather_labels_from_exprlist instantiated_expr_list in
              (new_preds',
                  E(Some(new_id),updated_expr_vars, sort_id,
                      ID(new_id,"("^(exprtype_to_string instantiated_expr1)^" WITH ."
                                  ^ (string_of_int num) ^" := "
                                  ^(exprtype_to_string instantiated_expr2)^")",accumulated_labels),label))
            else
              (new_preds',
                E(None,updated_expr_vars, sort_id,
                  FUN("TUPLE_WRITE",[instantiated_expr1;int_expr(num);instantiated_expr2]),label)) 
          | ("FLOOR",[expr1]) ->
              let (new_preds', instantiated_expr1)=
                  instantiate_expression new_preds instl expr1
              in
              if updated_freelist  =  [] then
                begin
                  let new_id=
                      make_floor ((instantiated_expr1))
                  in
                  term_db#add_term sort_id new_id;
                  (new_preds', E(Some new_id,updated_expr_vars, sort_id,
                    FUN("FLOOR",[instantiated_expr1]),label))
                end
              else
                (new_preds', E(None,updated_expr_vars, sort_id,
                  FUN("FLOOR",[instantiated_expr1]),label))
          |_->
            if updated_freelist  =  [] then
              let op_id=theory#decFunction name (theory#get_function_type name) in
              let arg_ids=List.map (fun (E(Some id,_,_,_,_)) ->
                                      if theory#is_bool id then
                                        mark_interpreted id;
                                      id)
                                   instantiated_expr_list
              in
              let function_id=theory#appFunction op_id arg_ids in
               term_db#add_term sort_id function_id;
               (new_preds',
                E(Some function_id,updated_expr_vars,sort_id,FUN(name,instantiated_expr_list),label))
            else
             (new_preds',
              E(None,updated_expr_vars,sort_id,FUN(name,instantiated_expr_list),label))
          end

      | BINOP(name,expr1,expr2) ->
          let (new_preds', instantiated_expr1)=
              instantiate_expression new_preds instl expr1
          in
          let (new_preds'',instantiated_expr2)=
              instantiate_expression new_preds' instl expr2
          in
          if updated_freelist  =  [] then
            let new_id=
                make_binop (BINOP(name,instantiated_expr1,instantiated_expr2))
            in
            term_db#add_term sort_id new_id;
            (new_preds'',
              E(Some new_id,updated_expr_vars,sort_id,
                BINOP(name,instantiated_expr1,instantiated_expr2),label))
          else
            (new_preds'',
              E(None,updated_expr_vars,sort_id,
                BINOP(name,instantiated_expr1,instantiated_expr2),label))
      | UMINUS(expr1) ->
          let (new_preds', instantiated_expr1)=
              instantiate_expression new_preds instl expr1
          in
          if updated_freelist  =  [] then
            let new_id=
                make_uminus (UMINUS(instantiated_expr1))
            in
            term_db#add_term sort_id new_id;
            (new_preds', E(Some new_id,updated_expr_vars, sort_id,UMINUS(instantiated_expr1),label))
          else
            (new_preds', E(None,updated_expr_vars, sort_id,UMINUS(instantiated_expr1),label))
      | VAR(var_id) -> 
                (try
                  let instantiation = List.assoc var_id instl in
                  let labeled_instantiation = add_label_to_expr instantiation label in
                    (new_preds,labeled_instantiation)
                with Not_found -> ( new_preds,expr)
                )
      | SYM(var_name) ->
          (new_preds,expr)
      | ID(_,_,_) ->
          (new_preds,expr)
      | INT(_) ->
          (new_preds,expr)
      | STRING(_) ->
          (new_preds,expr)
      end (* expr2 match *)
in
let rec traverse_and_skolemize (new_preds : Pred.pred list) pol (E(id,freevars,sort_id,expr2,label) as expr)=
  let rec make_skol_list pol binderl =
   (* this will take a binder list, along with the current polarity,
      If the polarity is positive, it will remove each existentially quantified
      variable from the beginning of the list, and associate it with
      a fresh constant.  It will also return the list of remaining binders after
      the end of the first set of existential quantifiers.

      If the polarity is negative, it will remove each universally quantified
      variable from the beginning of the list, and associate it with
      a fresh constant.  It will also return the list of remaining binders after
      the end of the first set of universal quantifiers.
   *)
        (match binderl with
          (FORALL,var_id,sort_id)::[] ->
            if pol  =  POS then
              ([],binderl)
            else
              let fresh_constant=define_fresh_constant var_id sort_id in
                ([var_id,fresh_constant ],[])

        | (EXISTS,var_id,sort_id)::[] ->
            if pol  =  NEG then
              ([],binderl)
            else
              let fresh_constant=define_fresh_constant var_id sort_id in
                ([var_id,fresh_constant],[])
        | (FORALL,var_id,sort_id)::t ->
            if pol  =  POS then
              ([],binderl)
            else
              let (instl,bindl)=make_skol_list pol t in
              let fresh_constant=define_fresh_constant var_id sort_id in
                ((var_id,fresh_constant )::instl,binderl)
        | (EXISTS,var_id,sort_id)::t ->
            if pol  =  NEG then
              ([],binderl)
            else
              let (instl,bindl)=make_skol_list pol t in
              let fresh_constant=define_fresh_constant var_id sort_id in
                ((var_id,fresh_constant)::instl,binderl)
        | [] -> ([],[])
        )
  in
  begin (* expr2 match *)
  match expr2 with
    NOT(e1)-> let (new_preds',skol_expr)=traverse_and_skolemize new_preds (negate pol) e1 in
                (new_preds',E(id,freevars,bool_type_id,NOT(skol_expr),label))
  | OR(el)-> 
            let (new_preds',skolemized_expr_list)=
                List.fold_left (
                    fun ((preds,el) as acc) (E(_,freevars2,_,_,_) as e1) ->
                         let (preds',skolemized_expr)=
                           traverse_and_skolemize preds pol e1
                         in
                         (preds',(skolemized_expr::el))
                               ) (new_preds,[]) el
            in
            (new_preds,E(id,freevars,bool_type_id,OR(skolemized_expr_list),label))
  | AND(el)->
            let (new_preds',skolemized_expr_list)=
                List.fold_left (
                    fun ((preds,el) as acc) (E(_,freevars2,_,_,_) as e1) ->
                         let (preds',skolemized_expr)=
                           traverse_and_skolemize preds pol e1
                         in
                         (preds',(skolemized_expr::el))
                               ) (new_preds,[]) el
            in
            (new_preds,E(id,freevars,bool_type_id,AND(skolemized_expr_list),Escjava_label.empty_label))

  | IFF(e1,e2)->(new_preds,expr)
  | QUANT(bindl,e1) ->
      let (instl,remainingbinders)= make_skol_list pol bindl in
        let (new_preds',replaced_expr)=instantiate_expression new_preds instl e1 in
          if remainingbinders  =  [] then
            traverse_and_skolemize new_preds' pol replaced_expr
          else
            (new_preds',E(id,freevars,bool_type_id,QUANT(remainingbinders,replaced_expr),label))
  | _-> (new_preds,expr)
  end (* expr2 match *)
in

object (self) (***********************************************)
initializer 
if Global.pars.have_random_seed then
begin
  Random.init (Global.pars.random_seed);
  if Global.pars.verbose then
    Printf.printf "\nSEEDING WITH SEED=%d\n" (Global.pars.random_seed)

end
else
begin
  Random.self_init(); 
  let random_seed=Random.int 1073741823 in
   Random.init(random_seed);
  if Global.pars.verbose then
   Printf.printf "\nRANDOM SEED = %d\n" random_seed
end

  val mutable current_depth = 0
  val mutable times_generate_terms_called = 0
  (* set to true when a round of generate_terms is done with no changes occuring in term_db *)
  val mutable no_new_terms = false
  (* set to true if instances_per_call should not increase *)
  val mutable instances_per_call_limit_reached = false
  
  val mutable times_generate_terms_called_consecutively=0
  val mutable times_instances_per_call_increased_consecutively=0
  
  val mutable initialization_finished = false


  method get_current_depth = current_depth

  method set_rand_seed seed = Printf.printf "\nRANDOM SEED SET TO %d\n" seed; Random.init(seed) 
  method get_number_of_instantiations() = !instantiations

  method set_max_instances_per_call max_instances = max_instances_per_call:= max_instances

  method skolemize expr =
    let (_,skol_expr)=traverse_and_skolemize [] POS expr in
      skol_expr
  
  method finish_initial_registration = 
    if not initialization_finished then
     begin
      self#generate_terms;
      initialization_finished <- true
     end
    else
      raise Skolemizer_already_initialized

  method private generate_terms =
    times_generate_terms_called <- times_generate_terms_called + 1;
    term_db#enable_term_registration;
    let generate_inst_lists freevars=
      let max_size=List.length freevars in
      let max_nums=(List.fold_left (fun max_term_acc (_,sort_id) ->
                                            ((term_db#get_term_db_size sort_id)-1)::max_term_acc
                                   ) [] freevars) 
      in
      let new_term_start_nums=(List.fold_left (fun new_term_acc (_,sort_id) ->
                                            ((term_db#get_first_new_term_position sort_id))::new_term_acc
                                   ) [] freevars) 
      in
      let rec generate_exhaustive_permutations current_size max_term_list new_term_used new_term_start_list numlist =    
          if current_size= max_size then
          begin
            [numlist]
          end
          else
          begin
            let lists= ref [] in 
              (match max_term_list,new_term_start_list with
                (max_term::remaining_max_term_list,new_term_start::remaining_new_term_start_list)->
                  (
                  if current_size=max_size-1 && (not new_term_used) && new_term_start>= max_term then
                    []
                  else if current_size=max_size-1 && (not new_term_used) && new_term_start<max_term then
                  begin
                    for num = new_term_start to max_term do 
                      lists:=(append_list (generate_exhaustive_permutations (current_size+1) (remaining_max_term_list) true remaining_new_term_start_list (num::numlist)) !lists) 
                    done;
                    !lists
                  end
                  else
                  begin
                    for num = 0 to max_term do 
                      let new_term_used_now = if num >= new_term_start then true else new_term_used in
                      lists:=(append_list (generate_exhaustive_permutations (current_size+1) (remaining_max_term_list) new_term_used_now remaining_new_term_start_list (num::numlist)) !lists) 
                    done;
                   !lists
                  end
                  )
              | _-> failwith("attempted to instantiate a term not covered in max_term_list")
              )
          end
      in
      let generate_random_permutations() =
        let instantiations_tbl=(Hashtbl.create 200 : (int list, bool) Hashtbl.t) in
        let instantiation_total= ref 0 in
        let instantiation_limit=10000 in
        let lists= ref [] in 
        for num = 0 to instantiation_limit do
          let numlist=List.map (fun max_num -> Random.int max_num) max_nums in
            if not (Hashtbl.mem instantiations_tbl numlist) then
            begin
              Hashtbl.add instantiations_tbl numlist true;
              lists:= numlist::(!lists)
            end
        done;
        !lists
      in
      let make_template_inst_list term_indexl =
        List.fold_left2 (fun acc (var_name,sort_id) term_index -> 
                          let term_id=term_db#get_term_id sort_id term_index in
                          let id_name = "i"^(string_of_int term_id)^"?" in
                          let expr_to_instantiate = E(Some term_id,empty_expression_variables,sort_id,ID(term_id,id_name,[]),Escjava_label.empty_label) in
                            (var_name,expr_to_instantiate)::acc
                        ) [] freevars term_indexl
      in
      let permutation_list =
          if Global.pars.exhaustive then 
            generate_exhaustive_permutations 0 max_nums false new_term_start_nums [] 
          else
            generate_random_permutations()
      in
      List.map (make_template_inst_list) permutation_list
    in
      
    List.iter (fun template -> 
                let freevars=template.Term_db.freevariables in
                let term=template.Term_db.term in
                let inst_lists =generate_inst_lists freevars in
                  List.iter (fun inst_list -> instantiate_expression [] inst_list term; () ) inst_lists
          ) term_db#get_templates;
    term_db#mark_new_terms;
    term_db#disable_term_registration;
    if term_db#term_db_has_been_updated_since_registration_enabled then
      no_new_terms <- true

  method private instantiate new_preds pol max_instances expr =
  match expr with E(id,expr_vars,_,QUANT(bindl,subexpr),_) ->
    let instance_total=ref 0 in
    let (existential_vars,universal_vars)=
                (match pol with
                  POS -> (expr_vars.existential_bound_vars,expr_vars.universal_bound_vars)
                | NEG -> (expr_vars.universal_bound_vars,expr_vars.existential_bound_vars)
                )
    in

    let make_inst_list() =
    (* this will take a binder list, along with the current polarity,
       and return a (var_name * term) list where term is a fresh constant
       if:
        the polarity is positive and the quantifier is EXISTS,
                or
        the polarity is negative and the quantifier is FORALL
       Otherwise, it will call the function get_term_to_instantiate_var to get
       an actual instantiation for var_name, and set term to that.
    *)
      let existential_inst_list=
            List.fold_left ( fun inst_list (var_id,sort_id) ->
                                  let fresh_constant=define_fresh_constant var_id sort_id in
                                    (var_id,fresh_constant)::inst_list
                           ) [] existential_vars
      in
      let total_inst_list=
            List.fold_left ( fun inst_list (var_id,sort_id) ->
                                  let expr_to_instantiate = get_expr_to_instantiate_var sort_id var_id in
                                    (var_id,expr_to_instantiate)::inst_list
                           ) existential_inst_list universal_vars
      in
      total_inst_list
  in
  let instantiations_tbl=(Hashtbl.create 200 : ((Global.var_id*Formula.exprtype) list, bool) Hashtbl.t) 
  in
  let rec make_inst_lists acc n =
      if(!instance_total<Global.pars.max_instance_lemmas/2) then
      begin
        if(n>0) then
        begin
          let instl=(make_inst_list()) in
            if(not (Hashtbl.mem instantiations_tbl instl)) then
            begin
              instance_total:= !instance_total+1;
              Hashtbl.replace instantiations_tbl instl true;
              make_inst_lists (instl::acc) (n-1)
            end
            else
              make_inst_lists acc (n-1)
        end
        else
          acc
      end
      else
        acc
  in
  let make_specific_inst_list term_indexl=
      let existential_inst_list=
            List.fold_left ( fun inst_list (var_id,sort_id) ->
                                  let fresh_constant=define_fresh_constant var_id sort_id in
                                    (var_id,fresh_constant)::inst_list
                           ) [] existential_vars
      in
      let total_inst_list=
            List.fold_left2 ( fun inst_list (var_id,sort_id) term_index ->
                                let term_id=term_db#get_term_id sort_id term_index in
                                let id_name = "i"^(string_of_int term_id)^"?" in
                                let expr_to_instantiate = E(Some term_id,empty_expression_variables,sort_id,ID(term_id,id_name,[]),Escjava_label.empty_label) in
                                  (var_id,expr_to_instantiate)::inst_list
                           ) existential_inst_list universal_vars term_indexl
      in
      total_inst_list
  in
  let rec make_complete_inst_lists() =
      let max_size=List.length universal_vars in
      let max_nums=(List.fold_left (fun max_term_acc (_,sort_id) ->
                                            ((term_db#get_term_db_size sort_id)-1)::max_term_acc
                                   ) [] universal_vars) 
      in
      let new_term_start_nums=(List.fold_left (fun new_term_acc (_,sort_id) ->
                                            ((term_db#get_first_new_term_position sort_id))::new_term_acc
                                   ) [] universal_vars) 
      in
      let instance_total = ref 0 in
      let rec generate_permutations current_size max_term_list new_term_used new_term_start_list numlist =    
        if !instance_total<Global.pars.max_instance_lemmas/2 then
        begin
          if current_size= max_size then
          begin
            instance_total:= !instance_total+1;
            [numlist]
          end
          else
          begin
            let lists= ref [] in 
              (match max_term_list,new_term_start_list with
                (max_term::remaining_max_term_list,new_term_start::remaining_new_term_start_list)->
                  (
                  if current_size=max_size-1 && (not new_term_used) && new_term_start>= max_term then
                    []
                  else if current_size=max_size-1 && (not new_term_used) && new_term_start<max_term then
                  begin
                    for num = new_term_start to max_term do 
                      lists:=(append_list (generate_permutations (current_size+1) (remaining_max_term_list) true remaining_new_term_start_list (num::numlist)) !lists) 
                    done;
                    !lists
                  end
                  else
                  begin
                    for num = 0 to max_term do 
                      let new_term_used_now = if num >= new_term_start then true else new_term_used in
                        lists:=(append_list (generate_permutations (current_size+1) (remaining_max_term_list) new_term_used_now remaining_new_term_start_list (num::numlist)) !lists) 
                    done;
                   !lists
                  end
                  )
              | _-> failwith("attempted to instantiate a term not covered in max_term_list")
              )
          end
        end
        else
        begin
          []
        end
      in
      let permutation_list=generate_permutations 0 max_nums false new_term_start_nums [] in
        List.map (make_specific_inst_list) permutation_list
  in      


      let numInstances= !max_instances_per_call in
      let instll=(if (Global.pars.exhaustive) then 
                   make_complete_inst_lists()
                 else 
                   make_inst_lists [] numInstances)
      in
      let (new_preds',instantiated_expr_list)=
          List.fold_left (fun acc instl->
                            match acc with
                             (new_preds,exprlist)->
                               let (new_preds',instantiated_expr)=
                                (instantiate_expression new_preds instl subexpr)
                               in
                               (new_preds',instantiated_expr::exprlist)
                         ) (new_preds,[]) instll
      in
      (new_preds',instantiated_expr_list)
  | _ -> raise Expression_must_be_quantified


(* problem arises here when the id is None *)
  method declare_abstractions_and_normalize new_preds expr level depth =
(*  This function will declare every remaining quantifier as an abstraction, so
    the cnf conversion can just stop and go back up whenever it
    sees a quantifier

    Assumes that skolemize has been called first(otherwise it will abstract
    existential quantifiers too)
*)
  match expr with 
   (E(Some id,expr_vars,sort_id,expr2,label))->
  (match expr2 with
    NOT(e1)->
      let (new_preds',normalized_expr)=
          self#declare_abstractions_and_normalize new_preds e1 level (depth+1)
      in
      (new_preds',E(Some id,expr_vars,bool_type_id,NOT(normalized_expr),label))
  | OR(el)->
      let (new_preds',normalized_expr_list)=
          List.fold_left
            (fun acc e -> match acc with
                            (preds,el)->
                              let (new_preds', normalized_expr )=
                                 self#declare_abstractions_and_normalize preds e level (depth+1)
                              in
                              (new_preds', normalized_expr::el)
            ) (new_preds,[]) el
      in
      (new_preds',E(Some id,expr_vars,bool_type_id,OR(normalized_expr_list),label))
  | AND(el)->
      let (new_preds',normalized_expr_list)=
          List.fold_left
            (fun acc e -> match acc with
                            (preds,el)->
                              let (new_preds', normalized_expr )=
                                 self#declare_abstractions_and_normalize preds e level (depth+1)
                              in
                              (new_preds', normalized_expr::el)
            ) (new_preds,[]) el
      in
      (new_preds',E(Some id,expr_vars,bool_type_id,AND(normalized_expr_list),label))
  | IFF(e1,e2)->
      let (new_preds',normalized_expr1)=
          self#declare_abstractions_and_normalize new_preds e1 level (depth+1)
      in
      let (new_preds'',normalized_expr2)=
          self#declare_abstractions_and_normalize new_preds' e2 level (depth+1)
      in
      (new_preds'',E(Some id,expr_vars,bool_type_id,IFF(normalized_expr1,normalized_expr2),label))
  | QUANT(bindl,((E(_,expr_vars2,_,_,_)) as quantified_expr))->
(*      Printf.printf "\ndeclare_abstractions_and_normalize quant";
      Formula.print_exprtype expr;
      Printf.printf "\n";*)
      (match bindl with
        (FORALL, _,_)::t ->
          add_quantified_abstraction id expr level depth;
          (if(List.for_all is_forall bindl)
           then
             let quantified_pred=pred_db#get_pred id in
               Pred.set_universal quantified_pred true
          );
          (new_preds,expr)
      | (EXISTS, _,_)::t ->
            let inner_not= Formula.make_not_expr quantified_expr in
            let flipped_quantifier=
              QUANT(List.map flip_binder bindl,inner_not)
            in
            let expr_to_abstract=E(Some id,expr_vars,bool_type_id,flipped_quantifier,Escjava_label.empty_label) in
              add_quantified_abstraction id expr_to_abstract level (depth+1);
              (if(List.for_all is_forall bindl) then
                let quantified_pred=pred_db#get_pred id in
                  Pred.set_universal quantified_pred true
              );
              let normalized_expr = Formula.make_not_expr expr_to_abstract in
              (match normalized_expr with
                E(Some newpred_id,_,_,_,_) -> 
                    let newpred = pred_db#get_pred newpred_id in
                      (newpred::new_preds,E(Some newpred_id,expr_vars,bool_type_id,
                                            NOT(expr_to_abstract),Escjava_label.empty_label))
              | _ -> failwith "declare_abstractions_and_normalize, make_not_expr should have assigned a predicate already"
              )
      | []-> raise Quantifier_must_have_binders
      )
  | _ -> (new_preds,expr)
  )
  | _ -> Printf.printf "\n\n";Formula.print_exprtype expr; failwith "\nin declare abstractions and normalize\n";

(* cl = accumulated clause list
   fl = formula list to be converted
 *)
(* pol is the polarity of the current subexpression, before any conversion *)
(* note that it stops changing a the literal level *)
  method private convert new_preds pol cl el level depth =
  let set_pred_guard_from_lit_id lit_id =
          let pred=Pred.pred (pred_db#get_lit lit_id) in
            Pred.set_guard pred
  in
  let set_pred_depth_from_lit_id lit_id depth=
          let pred=Pred.pred (pred_db#get_lit lit_id) in
            Pred.set_formula_depth pred depth
  in
  let generate_lemmas new_preds cl =
   function (E(Some n,expr_vars, _,QUANT(bindl,e1),label)) ->
     (if(List.for_all is_forall bindl) then
        let new_pred_id_for_not= make_uninterpreted_pred() in
        let new_pred_for_not = pred_db#get_pred new_pred_id_for_not in
        
        let not_expr=E(Some new_pred_id_for_not,Formula.reverse_variable_polarity expr_vars,bool_type_id,NOT(e1),Escjava_label.empty_label) in
        let new_quant_id=Some(make_quant_abs_pred()) in
        let negated_expr=
          E(new_quant_id,expr_vars, bool_type_id,QUANT(List.map flip_binder bindl,not_expr),Escjava_label.empty_label)
        in
(*        Printf.printf "\nnegated_expr=";
        Formula.print_exprtype negated_expr;
        Printf.printf "\n";
*)
         let (new_preds',skolemized_negated_expr)=traverse_and_skolemize (new_pred_for_not::new_preds) POS negated_expr in 
         let (new_preds'',normalized_negated_expr)=
            self#declare_abstractions_and_normalize (new_preds')
                                          skolemized_negated_expr level depth
         in
(*         Printf.printf "\nnormalized_negated_expr=";
         Formula.print_exprtype normalized_negated_expr;
         Printf.printf "\n";
*)
         match normalized_negated_expr with
           E(Some n1,_,_,_,_) ->
              set_pred_depth_from_lit_id n1 depth;
              let cl'=[n;n1]::cl in
                self#convert new_preds'' POS cl' [normalized_negated_expr] level (depth+1)
     else
       (new_preds,cl)
     )
   | _ -> (new_preds,cl)
  in
  match el with
      [] -> (new_preds,cl)
    | e::t ->
  match e with
      E (Some n, _, _,REL(_,_,_),pred_label) ->
        set_pred_depth_from_lit_id n depth;
        register_predicate_label_from_expr e;
        if pred_label#is_guard then
          set_pred_guard_from_lit_id n;
        self#convert new_preds pol (cl) t level depth(**)
    | E (Some n, _, _, PRED(_,_),pred_label) ->
        set_pred_depth_from_lit_id n depth;
        register_predicate_label_from_expr e;
        if pred_label#is_guard then
          set_pred_guard_from_lit_id n;
        self#convert new_preds pol (cl) t level depth(**)
    | E (Some n, _, _, ID(_,_,_),pred_label) ->
        set_pred_depth_from_lit_id n depth;
        if pred_label#is_guard then
          set_pred_guard_from_lit_id n;
        self#convert new_preds pol (cl) t level depth(**)
    | E (Some n, freevars, _, QUANT(bindl,e1),label) ->
        set_pred_depth_from_lit_id n depth;
        if label#is_guard then
          set_pred_guard_from_lit_id n;
        let (new_preds', cl')=generate_lemmas new_preds cl e in
          self#convert new_preds' pol (cl') t level depth
    | E (Some n, _, _, NOT e1,label) ->
        set_pred_depth_from_lit_id n depth;
        if label#is_guard then
          set_pred_guard_from_lit_id n;
        (let (new_preds',cl')=generate_lemmas new_preds cl e in
        let neg_pol = negate pol in
        match pol with
          POS->   (match e1 with
                  E (Some n1, _, _, REL(_,_,_),pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    register_predicate_label_from_expr e1;
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                    self#convert new_preds' pol ([compl n; compl n1]::cl') t level depth
                | E (Some n1, _, _, PRED(_,_),pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    register_predicate_label_from_expr e1;
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                    self#convert new_preds' pol ([compl n;compl n1]::cl') t level depth
                | E (Some n1, _, _, QUANT(_,_),pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                      self#convert new_preds' pol ([compl n;compl n1]::cl') t level depth
                | E (Some n1, _, _, f1',pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                    let cl'' = [compl n; compl n1]::cl' in (**)
                      let (new_preds'',l1)=
                          self#convert new_preds' neg_pol cl'' [e1] level (depth+1)
                      in
                      self#convert new_preds'' pol l1 t level depth
                )
        | NEG->  (match e1 with
                  E (Some n1, _, _, REL(_,_,_),pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    register_predicate_label_from_expr e1;
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                    self#convert new_preds pol ([n; n1]::cl') t level depth
                | E (Some n1, _, _, PRED(_,_),pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    register_predicate_label_from_expr e1;
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                    self#convert new_preds pol ([n; n1]::cl') t level depth
                | E (Some n1, _, _, QUANT(_,_),pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                    self#convert new_preds pol ([n; n1]::cl') t level depth
                | E (Some n1, _, _, e1',pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                    let cl'' = [compl n;compl n1]::[n; n1]::cl' in (**)
                      let (new_preds'',l1)=
                          self#convert new_preds' neg_pol cl'' [e1] level (depth+1)
                      in
                      self#convert new_preds'' pol l1 t level depth
                | E (None, _, _, _,_) -> failwith "skolemizer#convert(NOT) None"
                )
        | BOTH-> (match e1 with
                  E (Some n1, _, _, REL(_,_,_),pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    register_predicate_label_from_expr e1;
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                    self#convert new_preds pol ([compl n;compl n1]::[n; n1]::cl') t level depth
                | E (Some n1, _, _, PRED(_,_),pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    register_predicate_label_from_expr e1;
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                    self#convert new_preds pol ([compl n;compl n1]::[n; n1]::cl') t level depth
                | E (Some n1, _, _, QUANT(_,_),pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                      self#convert new_preds pol ([compl n;compl n1]::[n; n1]::cl') t level depth
                | E (Some n1, _, _, e1',pred_label) ->
                    set_pred_depth_from_lit_id n1 (depth+1);
                    if pred_label#is_guard then
                      set_pred_guard_from_lit_id n1;
                    let cl'' = [compl n;compl n1]::[n; n1]::cl' in (**)
                      let (new_preds'',l1)=
                          self#convert new_preds' neg_pol cl'' [e1] level (depth+1)
                      in
                      self#convert new_preds'' pol l1 t level depth
                )
        )
    | E (Some n, _, _, AND l,pred_label) ->
        (set_pred_depth_from_lit_id n depth;
         let pred=Pred.pred (pred_db#get_lit n) in
        if pred_label#is_guard then
          set_pred_guard_from_lit_id n;
        let (new_preds',cl')=
            List.fold_left (fun acc e ->
                              match acc with
                                (new_preds,cl)->
                                  generate_lemmas new_preds cl e
                           ) (new_preds,cl) l
         in
        match pol with
          POS->
                Pred.set_preferred_polarity pred Global.Valid;
                let cl'' = List.fold_left (**)
                (fun cl (E (Some m, _, _, _,_)) -> ([compl n; m])::cl) cl' l
                in
                let l1 = (List.fold_left
                                   (fun cl (E(Some m,_,_,_,pred_label) as e1) ->
                                    set_pred_depth_from_lit_id m (depth+1);
                                    if is_lit e1 then 
                                    begin
                                      register_predicate_label_from_expr e1;
                                      if pred_label#is_guard then
                                        set_pred_guard_from_lit_id m;
                                      cl
                                    end  
                                    else 
                                      e1::cl
                                   ) [] l) in
                let (new_preds'',l2)=self#convert new_preds' pol cl'' l1 level (depth+1) in
                self#convert new_preds'' pol l2 t level depth

        | NEG->
                Pred.set_preferred_polarity pred Global.Unsat;
                let c = n::(List.map (fun (E (Some m, _, _, _,_)) -> compl m) l) in
                let l1 = (List.fold_left
                                   (fun cl (E(Some m,_,_,_,pred_label) as e1) ->
                                    set_pred_depth_from_lit_id m (depth+1);
                                    if is_lit e1 then 
                                    begin
                                      register_predicate_label_from_expr e1;
                                      if pred_label#is_guard then
                                        set_pred_guard_from_lit_id m;
                                      cl
                                    end
                                    else 
                                      e1::cl
                                   ) [] l) in
                let (new_preds'',l2)=self#convert new_preds' pol (c::cl') l1 level (depth+1) in
                self#convert new_preds'' pol l2 t level depth
        | BOTH ->
                Pred.set_preferred_polarity pred Global.Undet;
                let cl'' = List.fold_left (**)
                (fun cl (E (Some m, _, _, _,_)) -> ([compl n; m])::cl) cl' l
                in
                let c = n::(List.map (fun (E (Some m, _, _, _,_)) -> compl m) l) in
                let l1 = (List.fold_left
                                   (fun cl (E(Some m,_,_,_,pred_label) as e1) ->
                                    set_pred_depth_from_lit_id m (depth+1);
                                    if is_lit e1 then 
                                    begin
                                      register_predicate_label_from_expr e1;
                                      if pred_label#is_guard then
                                        set_pred_guard_from_lit_id m;
                                      cl
                                    end
                                    else 
                                      e1::cl
                                   ) [] l) in
                let (new_preds'',l2)=self#convert new_preds' pol (c::cl'') l1 level (depth+1) in
                self#convert new_preds pol l2 t level depth
        )
    | E (Some n, _, _, OR l,pred_label) ->
        (set_pred_depth_from_lit_id n depth;
        let pred=Pred.pred (pred_db#get_lit n) in
        if pred_label#is_guard then
          set_pred_guard_from_lit_id n;
        let (new_preds',cl')=
            List.fold_left (fun acc e ->
                              match acc with
                                (new_preds,cl) ->
                                  generate_lemmas new_preds cl e
                           ) (new_preds,cl) l
         in
         match pol with
           POS ->
             Pred.set_preferred_polarity pred Global.Unsat;
             let c = (compl n)::(List.map (fun (E (Some m, _, _, _,_)) -> m) l) in
             let l1 = (List.fold_left
                                (fun cl (E(Some m,_,_,_,pred_label) as e1) ->
                                 set_pred_depth_from_lit_id m (depth+1);
                                 if is_lit e1 then 
                                 begin
                                   register_predicate_label_from_expr e1;
                                   if pred_label#is_guard then
                                     set_pred_guard_from_lit_id m;
                                   cl
                                 end
                                 else 
                                  e1::cl
                                ) [] l) in
             let (new_preds'',l2)=self#convert new_preds' pol (c::cl') l1 level (depth+1) in
             self#convert new_preds'' pol l2 t level depth
         | NEG->
             Pred.set_preferred_polarity pred Global.Valid;
             let cl'' = List.fold_left (**)
                (fun cl (E (Some m, _, _, _,_)) -> ([n; compl m])::cl) cl' l
             in
             let l1 = (List.fold_left
                                (fun cl (E(Some m,_,_,_,pred_label) as e1) ->
                                 set_pred_depth_from_lit_id m (depth+1);
                                 if is_lit e1 then 
                                 begin
                                   register_predicate_label_from_expr e1;
                                   if pred_label#is_guard then
                                     set_pred_guard_from_lit_id m;
                                   cl
                                 end
                                 else 
                                   e1::cl
                                ) [] l) in
             let (new_preds'',l2)=self#convert new_preds' pol (cl'') l1 level (depth+1) in
             self#convert new_preds'' pol l2 t level depth
         | BOTH->
             Pred.set_preferred_polarity pred Global.Undet;
             let cl'' = List.fold_left (**)
                 (fun cl (E (Some m, _, _, _,_)) -> ([n; compl m])::cl) cl' l
             in
             let c = (compl n)::(List.map (fun (E (Some m, _,_, _, _)) -> m) l) in
             let l1 = (List.fold_left
                                (fun cl (E(Some m,_,_,_,pred_label) as e1) ->
                                 set_pred_depth_from_lit_id m (depth+1);
                                 if is_lit e1 then 
                                 begin
                                   register_predicate_label_from_expr e1;
                                   if pred_label#is_guard then
                                     set_pred_guard_from_lit_id m;
                                   cl
                                 end
                                 else 
                                   e1::cl
                                ) [] l) in
             let (new_preds'',l2)=self#convert new_preds' pol (c::cl'') l1 level (depth+1) in
             self#convert new_preds'' pol l2 t level depth
        )
    | E (Some n, _, _, IFF (e1, e2),pred_label) -> (**)
        set_pred_depth_from_lit_id n depth;
        if pred_label#is_guard then
          set_pred_guard_from_lit_id n;
        let (new_preds1,cl1)=generate_lemmas new_preds cl e2 in
        let (new_preds',cl')=generate_lemmas new_preds1 cl1 e1 in
        let E (Some n1, _, _, e1',pred_label1) = e1 in
        let E (Some n2, _, _, e2',pred_label2) = e2 in
          set_pred_depth_from_lit_id n1 (depth+1);
          set_pred_depth_from_lit_id n2 (depth+1);

              let eli = if is_lit e1 then
                         begin
                           register_predicate_label_from_expr e1;
                           if pred_label1#is_guard then
                             set_pred_guard_from_lit_id n1;
                           if is_lit e2 then 
                           begin
                             register_predicate_label_from_expr e2;
                             if pred_label2#is_guard then
                               set_pred_guard_from_lit_id n2;
                             []
                           end
                           else 
                             [e2]
                         end
                       else
                         begin
                           if is_lit e2 then 
                           begin
                             register_predicate_label_from_expr e2;
                             if pred_label2#is_guard then
                               set_pred_guard_from_lit_id n2;
                             [e1]
                           end
                           else 
                             [e1;e2]
                         end
              in
        let cl'' = match pol with
                  |POS ->   [compl n;compl n1; n2]::
                            [compl n; n1;compl n2]::cl'
                  |NEG ->   [ n; n1; n2]::
                            [ n;compl n1;compl n2 ]::cl'
                  |BOTH ->  [compl n;compl n1; n2]::
                            [compl n; n1;compl n2]::
                            [ n; n1; n2]::
                            [ n;compl n1;compl n2 ]::cl'
        in
        let (new_preds'',l1) =self#convert new_preds' BOTH cl'' eli level (depth+1) in
        self#convert new_preds'' pol l1 t level depth
    | E (Some n,_,_,SYM(_),_) -> failwith "convert_new_preds SYM"
    | E (Some n,_,_,VAR(_),_) -> failwith "convert_new_preds VAR"
    | E (Some n,_,_,ITE(_,_,_),_) -> failwith "convert_new_preds ITE"
    | E (Some n,_,_,_,_) -> failwith "convert_new_preds mismatch"
    | E (None, _, _, _,_) -> failwith "convert_new_preds None"


  method to_cnf (E(Some n,_,_,_,_) as e) =
    let (_,cl) = self#convert [] POS [[n]] [e] 0 0 in
      cl

  method get_max_quant_depth = !max_quantifier_expansion_depth 

  method set_max_quant_depth max = max_quantifier_expansion_depth:=max

  method increment_max_quant_depth = max_quantifier_expansion_depth:=!max_quantifier_expansion_depth

  method get_max_instances_per_instantiate = !max_instances_per_call

  method set_max_instances_per_instantiate max= max_instances_per_call:=max

  method increase_max_instances_per_instantiate = max_instances_per_call:=!max_instances_per_call+Global.pars.max_instances

  method deepen = 
         let how_to_deepen() = 
           if no_new_terms then
             if not instances_per_call_limit_reached then
              begin
               times_instances_per_call_increased_consecutively <- times_instances_per_call_increased_consecutively + 1;
               times_generate_terms_called_consecutively <- 0;
               "instances_per_call"
              end
             else "stop_don't know"
           else if instances_per_call_limit_reached then
            begin
             times_generate_terms_called_consecutively <- times_generate_terms_called_consecutively + 1;
             times_instances_per_call_increased_consecutively <- 0;
             "generate_terms"
            end
           else if times_generate_terms_called_consecutively = 0 then
            begin
             times_generate_terms_called_consecutively <- times_generate_terms_called_consecutively + 1;
             times_instances_per_call_increased_consecutively <- 0;
             "generate_terms"
            end
           else 
            begin
             times_instances_per_call_increased_consecutively <- times_instances_per_call_increased_consecutively + 1;
             times_generate_terms_called_consecutively <- 0;
             "instances_per_call"
            end
         in    
         current_depth <- current_depth + 1;
         let deepen_type=how_to_deepen() in
         if deepen_type="generate_terms" then
          begin
           self#generate_terms;
           (* if nothing was accomplished in the generation phase ask for the new choice *)
           if no_new_terms=true then
             let new_deepen_type = how_to_deepen() in
             if deepen_type="instances_per_call" then
               self#increase_max_instances_per_instantiate
             else if deepen_type="stop_don't know" then
               raise Dontknow
          end
         else if deepen_type="instances_per_call" then
           self#increase_max_instances_per_instantiate
         else if deepen_type="stop_don't know" then
           raise Dontknow
         else failwith "how_to_deepen returned an illegal value";
         self#generate_terms;

  method clear = Hashtbl.clear quantified_abstraction_table; 
                 Hashtbl.clear positive_instantiation_table; 
                 Hashtbl.clear negative_instantiation_table;
                 done_instantiating:=false;
                 max_instances_per_call:=Global.pars.max_instances;
                 times_generate_terms_called_consecutively <- 0;
                 times_instances_per_call_increased_consecutively <- 0;
                 current_depth <- 0;
                 max_quantifier_expansion_depth:=0


  method reset = 
                 Hashtbl.iter (fun key (expr,level,_) -> 
                              if level>1 then Hashtbl.remove quantified_abstraction_table key
                              ) quantified_abstraction_table;
                 Hashtbl.clear positive_instantiation_table; 
                 Hashtbl.clear negative_instantiation_table;
                 done_instantiating:=false;

  method remove_pred id = Hashtbl.remove quantified_abstraction_table id


  method get_new_lemmas =
    if Global.debug then Printf.printf "\nstarting get_new_lemmas\n";
    let max_instances=Global.pars.max_instances in
    let abstract_pred_ids = pred_db#quantified_partial_model in
    let activity=ref false in
    let lemmas_for_pred =
      fun (new_preds,cl) pred_id ->
        let pred=pred_db#get_pred pred_id in
        if(Hashtbl.mem quantified_abstraction_table pred_id) then
          let ((E(Some id,_,_,_,_) as e),level,depth) =lookup_quantified_abstraction pred_id in
          if(Pred.pred_status pred   =   Valid) then
          begin
(*            Printf.printf "\nlooked_up quantified_abstraction %d\nExpressions was=" pred_id;
            Formula.print_exprtype e;
            Printf.printf "\n";
*)
            activity:=true;
            if(not (instantiated_pos pred_id) && !max_quantifier_expansion_depth>=level && not (!done_instantiating)) then
            begin
              set_instantiated_positively pred_id;
              let (new_preds',instantiated_expr_list) =
                self#instantiate new_preds POS max_instances e
              in
              List.fold_left (
                fun (new_preds,cl) instantiated_expr ->
                  let (new_preds',skolemized_expr) = (traverse_and_skolemize new_preds POS instantiated_expr) in
                  let (new_preds'',(E(Some id2,_,_,_,_) as normalized_expr))=
                      self#declare_abstractions_and_normalize new_preds' skolemized_expr (level+1) depth
                  in
increase_instantiation_total();
(*
                  Printf.printf "\nNew lemma: %d -> (" id;
                  Formula.print_exprtype normalized_expr;
                  Printf.printf ")\n";
*)
                  self#convert new_preds'' POS ([compl id;id2]::cl) [normalized_expr] (level+1) depth
              ) (new_preds',cl) instantiated_expr_list
            end
            else
            begin
              (new_preds,cl)
            end
          end
          else if (Pred.pred_status pred   =   Unsat && not (Pred.is_universal pred))
          then
          begin
            activity := true;
            if(not (instantiated_neg pred_id) && !max_quantifier_expansion_depth>=level && not (!done_instantiating)) then
            begin
              set_instantiated_negatively pred_id;

              let (new_preds',instantiated_expr_list) =
                self#instantiate new_preds NEG max_instances e
              in
              List.fold_left (
                fun (new_preds,cl) (E(_,expr_vars,_,_,_) as instantiated_expr) ->
                  let not_id=make_uninterpreted_pred() in
                  let new_pred = pred_db#get_pred not_id in
                  let negated_instantiated_expr=
                    E(Some not_id,Formula.reverse_variable_polarity expr_vars,bool_type_id,NOT(instantiated_expr),Escjava_label.empty_label)
                  in
                  let (new_preds',skolemized_expr) = traverse_and_skolemize new_preds POS negated_instantiated_expr in

                  let (new_preds'',(E(Some id2,_,_,_,_) as normalized_expr))=
                      self#declare_abstractions_and_normalize new_preds' skolemized_expr (level+1) depth
                  in
increase_instantiation_total();
                  self#convert new_preds'' POS ([id;id2]::cl) [normalized_expr] (level+1) depth
              ) (new_preds',cl) instantiated_expr_list
            end
            else
            begin
              (new_preds,cl)
            end
          end
          else
            (new_preds,cl)
        else
          (new_preds,cl)
    in

    let (new_preds,cl)=
      List.fold_left lemmas_for_pred ([],[]) abstract_pred_ids
    in
    match !activity,cl with
      (false,_) -> (FINISH) (*new_preds,cl*)
    | (true,[]) -> done_instantiating:=true; (EXHAUSTED_INSTANTIATIONS) (*new_preds,cl*)
    | (true,cl) -> ( (*Printf.printf "\nnum clauses sent back=%d\n" (List.length cl); *)(INSTANTIATIONS(new_preds,cl))) (*new_preds,cl*)

end
