(*
This file is part of the first order theorem prover Darwin
Copyright (C) 2004, 2005, 2006
              The University of Iowa
              Universitaet Koblenz-Landau 

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.
*)



(*** types ***)


type config = Config.config
type statistic = Statistic.statistic
type counter = Counter.counter
type symbol = Symbol.symbol
type var = Var.var
type term = Term.term
type clause = Term.clause
type literal = Term.literal
type subst =  Subst.subst
type choice_point = State.choice_point
type state = State.state
type problem = Problem.problem
type bound = Bound.bound
type finite_domain = Finite_domain.finite_domain
type 'data stack = 'data Stack.stack


type element = {
  el_id: int;
  el_literal: literal;
  el_choice_point: choice_point;
  el_pars: var list;
  el_generation: int;
  el_is_fd_incomplete: bool;
  mutable el_compacted: int;
}


(*** representation ***)


let element_to_string (element: element) : string =
  "choice_point:    " ^ State.choice_point_to_string element.el_choice_point ^ "\n"
  ^ "literal: " ^ Term.literal_to_string element.el_literal ^ "\n"


(* context elements are stored in a term index *)
class index_data =
object
  method is_equal (first: element) (second: element) : bool =
    first == second
      
  method to_string (element: element) : string =
    element_to_string element
end
type predicate_index = element Term_indexing.predicate_index

(* a term index for each sign/predicate combination *)
type index = element Term_indexing.index


(* the context is (redundantly) stored in
- a list/stack for easy backtracking
- an index for fast term checks *)

type context = {
  (* environment *)
  statistic: statistic;
  config: config;
  state: state;

  (* the id counter for the context literals/elements.
     only positive, and local to this context,
     in contrast to unique_element_id_counter. *)

  id_counter: counter;

  (* the max. size of the context over the whole derivation.
     just for statistical purposes. *)

  mutable max_size: int;

  (* the context elements stored in a list / as a stack.
     ordered by increasing element id. *)

  mutable context: element stack;

  (* term indexing over the context. an index for each sign/predicate pair *)
  index: index;

  (* None --> context is universal.
     Some cp -> first cp in which the context become parametric. *)

  mutable universal: choice_point option;
}




(*** globals ***)


(* negative ids reserved for special global elements *)

let unique_element_id_counter =
  ref (-1)


let get_unique_element_id () : int =
  unique_element_id_counter := !unique_element_id_counter - 1;
  !unique_element_id_counter


(*** constants ***)


let null_element : element = {
  el_id = get_unique_element_id ();
  el_literal = Term.null_literal;
  el_pars = [];
  el_generation = 0;
  el_choice_point = State.invalid_choice_point;
  el_is_fd_incomplete = false;
  el_compacted = -1;
}

let assert_element = {
  el_id = get_unique_element_id ();
  el_literal = Term.assert_literal;
  el_choice_point = State.valid_choice_point;
  el_pars = [];
  el_generation = 0;
  el_is_fd_incomplete = false;
  el_compacted = -1;
}

let plus_v_element = {
  el_id = get_unique_element_id ();
  el_literal = Term.plus_v;
  el_choice_point = State.valid_choice_point;
  el_pars = Term.vars_of_literal Term.plus_v;
  el_generation = 0;
  el_is_fd_incomplete = false;
  el_compacted = -1;
}

let minus_v_element = {
  el_id = get_unique_element_id ();
  el_literal = Term.minus_v;
  el_choice_point = State.valid_choice_point;
  el_pars = Term.vars_of_literal Term.minus_v;
  el_generation = 0;
  el_is_fd_incomplete = false;
  el_compacted = -1;
}




(*** creation ***)


(* the context does not explicitely contain -v/+v,
   this information has to be retrieved from config *)

let create (config: config)
    (statistic: Statistic.statistic) (state: state) : context =

  let index =
    if Config.is_horn config then
      Discrimination_tree.create_index false (new index_data)
    else
      Discrimination_tree.create_index true (new index_data)
(*      Substitution_tree.create_index (new index_data)*)
  in
    {
      config = config;
      statistic = statistic;
      state = state;
      id_counter = Counter.create_with 0;
      max_size = 0;
      context = Stack.create null_element;
      index = index;
      universal = None;
    }










(*** representation ***)


let print_context (context: context) (out: out_channel) : unit =
  Print.output_line out (Term.literal_to_string (Config.default_v context.config));

  let elements =
    Stack.fold (fun acc x -> x :: acc) [] context.context
  in
  let elements =
    List.sort
      (fun x y ->
        Term.compare_literals x.el_literal y.el_literal
      )
      elements
  in
  List.iter
    (fun element ->
      Print.output_line out (Term.literal_to_string element.el_literal);
    )
    elements




let update_max_size (context: context) : unit =
  context.max_size <- Tools.max_int context.max_size (Stack.size context.context)


let print_max_size (context: context) : unit =
  Print.print_statistic "Maximum Context Size" context.max_size




(*** iteration ***)


let iter (func: element -> unit) (context: context) : unit =
  Stack.iter func context.context

let fold (func: '-> element -> 'a) (acc: 'a) (context: context) : 'a =
  Stack.fold func acc context.context






(*** access ***)


let element_equal (first: element) (second: element) : bool =
  first == second






let element_for_literal context literal =
  match (context.index#find literal)#find literal.Term.atom with
   | None ->
       (* compacted *)
       let found = ref None in
         begin
         try
         Stack.iter
           (fun element ->
              if Term.literal_equal element.el_literal literal then begin
                found := Some element;
                raise Exit
              end
           )
           context.context;
           !found
         with
           | Exit ->
               !found
         end

   | element ->
       element


let most_recent_element (context: context) =
  Stack.top context.context

let is_more_recent x y =
  compare x.el_id y.el_id > 0


let parameters_of_literal (literal: literal) : var list =
  List.find_all
    Var.is_parametric
    (Term.vars_of_literal literal)


(* compact the context with the given (new) literal *)
let compact (context: context) (index: predicate_index) (new_element: element) : unit =
  if not (Config.compact context.config) then begin
    ()
  end

  else begin
    (* find all compacted elements *)
    let compacted_elements =
      if Config.term_indexing context.config then
        index#find_all_instances ~p_preserving:true new_element.el_literal.Term.atom
      else
        Stack.fold
          (fun acc element ->
             try
               ignore (Unification.match_literals ~recompute:false ~p_preserving:true
                         new_element.el_literal 0
                         element.el_literal 1);
               element :: acc
             with
               | Unification.UNIFICATION_FAIL  ->
                   acc
          )
          []
          context.context
    in
      (* mark all uncompacted elements compacted *)
      List.iter
        (fun compacted_element ->
           (* already compacted by another literal? *)
           if compacted_element.el_compacted < 0 then begin
             (* no, so compact *)
             Statistic.inc_compact context.statistic;
             compacted_element.el_compacted <- new_element.el_id;
             let index =
               context.index#find compacted_element.el_literal
             in
               if not(
                 index#remove
                   compacted_element.el_literal.Term.atom
                   (Some compacted_element)
               )
               then
                 failwith "Context.compact: remove from index"  

           end
        )
        compacted_elements
  end


let add (context: context) (literal: literal) (generation: int) (is_fd_incomplete: bool)
    : element =

  (* check if context gets inconsistent *)
  if Const.debug then begin
    let index =
      context.index#find (Term.request_negated_literal literal)
    in
      match index#find_unifiable ~p_preserving:true literal.Term.atom with
        | None ->
            ()

        | Some element ->
            print_endline ("in context: " ^ Term.literal_to_string element.el_literal);
            print_endline ("add:        " ^ Term.literal_to_string literal);
            failwith ("Context.add: inconsistent: " ^ Term.literal_to_string literal);
  end;

  let pars =
    parameters_of_literal literal
  in
  begin
    match context.universal, pars with
      | None, _ :: _ ->
          (* first non-universal literal *)
          context.universal <- Some (State.active_choice_point context.state)

      | _ ->
          ()
  end;

  let id =
    try
      Counter.next context.id_counter
    with
      | Counter.OVERFLOW ->
          raise (Const.NO_SOLUTION "Context.add: context id overflow")
  in
  let new_context_element = {
    el_id = id;
    el_literal = literal;
    el_choice_point = State.active_choice_point context.state;
    el_pars = pars;
    el_generation = generation;
    el_is_fd_incomplete = is_fd_incomplete;
    el_compacted = -1;
  }
  in
  (* get the index where the literal must be inserted *)
  let index =
    context.index#find new_context_element.el_literal
  in

    compact context index new_context_element;

    (* add new literal to context and index *)
    Stack.push context.context new_context_element;
    update_max_size context;

    if Config.term_indexing context.config then begin
      index#add new_context_element.el_literal.Term.atom new_context_element
    end;

    (* all index entries correspond to the term they are stored under *)
    if Const.debug then begin
      context.index#iter
        (fun _ index ->
           index#iter
           (fun term element ->
              if not (Term.term_equal term element.el_literal.Term.atom) then begin
                print_endline ("\n\n\nFAIL=====\n");
                print_endline (index#to_string);
                print_endline ("ADD: " ^ Term.literal_to_string new_context_element.el_literal);
                print_endline ("Term             : " ^ Term.term_to_string term);
                print_endline ("But node contains: " ^ element_to_string element);
                failwith "Context.add"
              end
           )
        )
    end;
    
    new_context_element



let is_universal context =
  context.universal == None




(*** creation ***)


let from_file (file_name: string)
  (config: config) (statistic: statistic) (state: state) : context =
  let context =
    create config statistic state
  in

  let in_channel =
    open_in file_name
  in

  let rec read_literals () =
    try
      let line =
        input_line in_channel
      in
      let literal =
        Read_darwin.to_literal line
      in
        ignore (add context literal 0 false: element);
        read_literals ()
    with
      | End_of_file ->
          ()
  in
    read_literals ();
    close_in in_channel;
    context








(*** checks ***)



let check_contradictory (context: context) (literal: literal) : element option =
  let index =
    context.index#find (Term.request_negated_literal ~insert_db:false literal)
  in
    index#find_unifiable ~p_preserving:true literal.Term.atom

let check_subsumed (context: context) (literal: literal) : element option =
  let index =
    context.index#find literal
  in
    index#find_generalization ~p_preserving:true literal.Term.atom

let check_productive (context: context) (producer: literal) (produced: literal) : element option =

  let index_producer =
    context.index#find producer
  in
  let index_produced =
    context.index#find produced
  in
      

  (* check second part of productivity *)
  let do_check_productive () =
      (* if the producer is v then any literal more general than produced is shielding *)
      if Term.literal_equal producer (Config.default_v context.config) then begin
        index_produced#find_generalization ~p_preserving:false produced.Term.atom
      end
        
      (* for not mixed literals a simple shielding check is sufficient. *)
      else if not (Config.mixed_literals context.config) then begin
        index_produced#find_shielding producer.Term.atom produced.Term.atom
      end
        
      (* for mixed literals a shielding literal must also not be shielded itself. *)
      else begin
        let iterator =
          index_produced#get_shielding_iterator producer.Term.atom produced.Term.atom
        in
        let rec loop iterator =
          if iterator#is_empty then
            (* not shielded *)
            None
                  
          else
            (* is the shielding literal itself shielded? *)
            let shielding_element =
              iterator#next
            in
            let shield_for_shielding_element =
              index_producer#find_shielding
                shielding_element.el_literal.Term.atom
                produced.Term.atom
            in
              match shield_for_shielding_element with
                | None ->
                    Some shielding_element
                      
                | Some _ ->
                    loop iterator
        in
          loop iterator
      end
  in
    
  (* check first part of productivity, i.e. if producer is a most specific generalization.  *)
    if not (Config.mixed_literals context.config) then begin
      match index_producer#find_strongly_shielding producer.Term.atom produced.Term.atom with
        | None ->
            do_check_productive ()
              
        | Some _ as conflict ->
            conflict
    end
    else begin
      do_check_productive ()
    end


let check_productive (context: context) (producer: literal) (produced: literal) : element option =

  if not (Config.productivity context.config) then begin
    None
  end

  (* must be productive if the context is not contradictory. *)
  else if Term.are_terms_variants producer.Term.atom produced.Term.atom then begin
    None
  end

  (* if the producer is universal (and not compacted) it must produce the instance
     if the context is not contradictory. *)

  else if not (Term_attributes.is_term_parametric producer.Term.atom) then begin
    None
  end

  (* do the check *)
  else begin
    let check =
      check_productive context producer produced
    in
      begin
        match check with
          | None ->
              ()
                
          | Some _ ->
              Statistic.inc_filtered_by_productivity context.statistic;
      end;
      check
  end

let find_all_unifiable (context: context) (literal: literal) : element list =
  let index =
    context.index#find literal
  in
    index#find_all_unifiable ~p_preserving:false literal.Term.atom


let find_all_subsuming (context: context) (literal: literal) : element list =
  let index =
    context.index#find literal
  in
    index#find_all_generalizations ~p_preserving:true literal.Term.atom







let rec is_true (context: context) (literal: literal) : bool =
  (* contradictory? *)
  match check_contradictory context literal with
    | Some _ ->
        false

    | None ->
        (* p-instance of a context literal? *)
        begin
          match check_subsumed context literal with
            | Some _ -> true

            | None ->
                (* produced? *)
                let generalizations =
                  (context.index#find literal)#find_all_generalizations
                    ~p_preserving:false literal.Term.atom
                in
                  List.exists
                    (fun generalization ->
                      match check_productive context
                        generalization.el_literal
                        (Term.request_negated_literal ~insert_db:false literal)
                      with
                        | None ->
                            (* a positive produced literal is true in the interpretation,
                               but for a negative one we have to check that the positive one
                               is not produced as well. *)

                            if literal.Term.sign then
                              true
                            else
                              not (is_true context (Term.request_negated_literal ~insert_db:false literal))
                        | Some _ -> false
                    )
                    generalizations
        end




(*** Backtracking ***)


(* removes all context literal added in invalid choice points,
   and resets the element counter *)

let backtrack_elements (context: context) : unit =

  (* remove an element from the index *)
  let remove_from_index (element: element) : unit =
    if Config.term_indexing context.config then begin
      let index =
        context.index#find element.el_literal
      in
      let removed : bool =
        index#remove
          element.el_literal.Term.atom
          (Some element)
      in
        if Const.debug then begin
          context.index#iter
            (fun _ index ->
               index#iter
                 (fun term element ->
                    if not (Term.term_equal term element.el_literal.Term.atom) then begin
                      print_endline ("\n\n\nFAIL=====\n");
                      print_endline (index#to_string);
                      print_endline ("REMOVE: " ^ Term.literal_to_string element.el_literal);
                      print_endline ("Term             : " ^ Term.term_to_string term);
                      print_endline ("But node contains: " ^ element_to_string element);
                      failwith "Context.backtrack"
                    end
                 )
              )
        end;
        
        if Const.debug && not removed then begin
          failwith "Context.backtrack index II"
        end
    end
  in
    
  (* all invalidated choice points are the at the top of the context.

     the id counter could be reset here,
     but that has bad interactions with the candidate queue.
     As a total ordering over all candidates uses the id of each used context element,
     and invalid candidates are only lazily removed,
     it can happen that different candidates have the same position in the ordering.
     that is, if an old invalid and a new valid candidate use context literals
     with the same id.

     the same happens with the more complicates scheme of set compression
     for candidates, basically whenever a total ordering over candidates is needed.

     Furthermore, if invalid candidates come earlier in the ordering they
     are also earlier removed.
  *)

  let rec backtrack_elements' () =
    if Stack.is_empty context.context then
      (*          Counter.set context.id_counter 0;*)
      ()

    else
      let element =
        Stack.top context.context
      in
        if State.is_choice_point_invalid element.el_choice_point then begin
          remove_from_index element;
          Stack.remove_top context.context;
          backtrack_elements' ();
        end
  in
    backtrack_elements' ()


(* uncompact all context literal compacted in invalid choice points *)
let backtrack_compact (context: context) : unit =
  if not (Config.compact context.config) then begin
    ()
  end

  else begin
    try
      let max_context_id =
        (most_recent_element context).el_id
      in
        Stack.iter
          (fun context_element ->
             if Const.debug && State.is_choice_point_invalid context_element.el_choice_point then begin
               failwith (
                 "Context.backtrack_compact: invalid context literal "
                 ^ Term.literal_to_string context_element.el_literal
                 ^ " in the context."
               );
             end
               
             else if context_element.el_compacted > max_context_id then begin
               context_element.el_compacted <- -1;
               let index =
                 context.index#find context_element.el_literal
               in
                 index#add context_element.el_literal.Term.atom context_element
             end
          )
          context.context
          
    with
      | Not_found ->
          (* empty context *)
          ()
  end

    
let backtrack (context: context) : unit =
  (* first remove all invalid literals *)
  backtrack_elements context;
  (* now undoe the compacting of now removed literals *)
  backtrack_compact context;

  match context.universal with
    | Some cp when State.is_choice_point_invalid cp ->
        context.universal <- None
    | _ -> ()
















(*** DIG ***)





(* in the dig representation a model is represented by positive atoms
   with a set of negative exception atoms.
   For -v, this is quite convenient.
   For +v, this implies that per default the most general version
   of a predicate (called schema term here) is true,
   if not overridden by the context.
   That is, for the predicate p of arity 3 per default p(u, v, w) is true,
   unless a variant of -p(u, v, w) is in the context.
   For a schema term the discrimination into universal and parametric variables
   is of no importance.
*)



(* an implicit generalization. *)
type ig = {
  (* the positive term *)
  term: term;
  (* the negative exceptions *)
  exceptions: term list;
}

(* a disjunction of implicit generalization. *)
type dig = ig list



let sort_digs (dig: dig) : dig =
  List.sort
    (fun x y ->
      Symbol.compare_name (Term.top_symbol_term x.term) (Term.top_symbol_term y.term)
    )
    dig



(* is term subsumed by any term in terms? *)
let is_exception_subsumed (term: term) (terms: term list) : bool =
  List.exists
    (fun old ->
      Unification.is_term_generalization ~p_preserving:false old term
    )
    terms

(* return the terms not subsumed by term. *)  
let subsume_exceptions (term: term) (terms: term list) : term list =
  List.find_all
    (fun old ->
      not (Unification.is_term_generalization ~p_preserving:false term old)
    )
    terms


(* is term subsumed by any ig in dig? *)
let is_subsumed_by_dig (term: term) (dig : dig) : bool =
  List.exists
    (fun ig ->
      (* is the dig term more general? *)
      Unification.is_term_generalization ~p_preserving:false ig.term term
      &&

      (* and none of its exceptions overlapping? *)
      List.for_all
        (fun exception_term ->
          (* either exception is an instance of the dig term -
             then the subsuming dig term produces the same instances
             as the subsumed dig term with this exception *)

          Unification.is_term_generalization ~p_preserving:false
            term exception_term
          ||
          (* or exception and dig term do not overlap -
             then the subsuming dig term produces the all instances
             of the subsumed dig term *)

          not (Unification.are_terms_unifiable ~p_preserving:false
            term exception_term)
        )
        ig.exceptions
    )
    dig
  

(* return the igs not subsumed by the ig *)  
let subsume_with_ig (ig: ig) (dig: dig) : dig =
  let subsume =
    [ig]
  in
    List.find_all
      (fun ig' ->
         not (is_subsumed_by_dig ig'.term subsume)
      )
      dig




(* get the dig representation of all predicates
   which are contained in the context as positive terms.
   for these, the needed schema terms are added as well.

   as compacted literals are not in the index,
   these are ignored automatically.
   literals outside the intended signature or are filtered explicitly
   using the predicate restrict_signature.
*)



(* in finite domain mode replace reltions by equations,
   e.g.
   r_2(z, x, y, 1)
   --> with the 2-ary function symbols f and g:
   f(x, y) = 1
   g(x, y) = 1
*)

let copy_index (context: context) (finite_domain: finite_domain option) : term Term_indexing.index =
  let new_index =
    Discrimination_tree.create_term_index false
  in
    match finite_domain with
      | None ->
          (* just copy the old index *)
          context.index#iter
            (fun literal index ->
              index#iter
                (fun term _data ->
                  if Term.is_input_term term then
                    (new_index#find literal)#add term term
                )
            );
          new_index
          
    | Some finite_domain ->
        (* copy and replace relations by equations *)
        context.index#iter
          (fun literal index ->
            index#iter
              (fun term _data ->
                (* contains special symbol, skolem, splitting, ... - ignore *)
                if not (Term.is_fd_term term) then begin
                  ()
                end
                  
                (* replace relation by all function instances *)
                else if Term.is_fd_relation literal then begin
                  let equations =
                    Finite_domain.relation_to_equations finite_domain term
                  in
                    List.iter
                      (fun equation ->
                        (* if the relation symbol was based on a skolem function,
                           as introduced by term definitions (Const.fd_use_term_definitions),
                           then the function is not over the input signature. *)

                        if Term.is_fd_term equation then begin
                          let literal =
                            Term.request_literal literal.Term.sign equation
                          in
                            (new_index#find literal)#add equation equation
                        end
                      )
                      equations
                end
                
                (* replace diff by equality *)
                else if Symbol.equal Symbol.diff (Term.top_symbol_term term) then begin
                  match term with
                    | Term.Func func ->
                        let equation =
                          Term.request_func (Symbol.equality, func.Term.subterms)
                        in
                        let literal =
                          Term.request_literal (not literal.Term.sign) equation
                        in
                          (new_index#find literal)#add equation equation

                    | _ ->
                        failwith "Context.copy_index: diff"
                end

                (* normal predicate, just keep *)
                else if Symbol.is_input (Term.top_symbol_literal literal) then begin
                  (new_index#find literal)#add term term
                end
                  
                (* ? *)
                else begin
                  failwith "Context.copy_index"
                end
              )
          );
        new_index


(* assure that all positive schema terms are added for +v *)
let add_schemas (index: term Term_indexing.index) (dig: dig) (problem: problem) : dig =
  begin
    let predicates =
      if problem#containsEquality then
        Symbol.equality :: problem#getPredicateSymbols
      else
        problem#getPredicateSymbols
    in
      List.fold_left
        (fun acc predicate ->
           (* predicate already processed when creating the digs
              from the context? *)

           if
             List.exists
               (fun ig ->
                  match ig.term with
                    | Term.Const symbol
                    | Term.Func { Term.symbol = symbol } (*(symbol, _, _)*) ->
                        Symbol.equal predicate symbol
                          
                    | _ ->
                        false
               )
               dig
           then
             acc

           else begin
             let schema_term =
               Term.create_schema_term predicate
             in
             let exception_index =
               index#find
                 (Term.request_literal false schema_term)
             in
               (* schema already included in negative terms? *)
               if
                 try
                   exception_index#iter
                     (fun term _ ->
                        if Term.is_schema_term term then raise Exit
                     );
                   false
                 with
                   | Exit ->
                       true
               then
                 acc
                   
               (* add the schema term *)
               else
                 (* get all exceptions of the term which are instances *)
                 let instance_exceptions =
                   exception_index#find_all_instances ~p_preserving:false schema_term
                 in
                 
                 (* remove subsumed exceptions. *)
                 let pruned_exceptions =
                   List.fold_left
                     (fun acc current ->
                        if is_exception_subsumed current acc then
                          acc
                            
                        else
                          current :: subsume_exceptions current acc
                     )
                     []
                     instance_exceptions
                 in
                   
                 (* create the ig *)
                 let ig = {
                   term = schema_term;
                   exceptions = List.sort Term.compare_terms pruned_exceptions;
                 }
                 in
                   ig :: acc
           end
        )
        dig
        predicates
  end
    



let get_dig (context: context) (problem: problem) (finite_domain: finite_domain option) : dig =
  (* for finite domain copy context and replace relations by functions *)
  let index =
    copy_index context finite_domain
  in

  (* assemble the dig of this predicate by adding the exceptions to the terms. *)
    (* terms: positive term instances *)
    (* exception index: index over negative term instances *)
  let assemble_dig terms exception_index =
    List.fold_left
      (fun acc term ->
        (* in finite domain mode remove all equalities/disequalities
           not over function symbols.
           they are not needed anyway,
           and otherwise e.g. x = 1 might subsume all functions *)

        if begin
          match finite_domain with
            | None -> false
            | Some _ ->
                begin
                  match term with
                    | Term.Func func when Symbol.equal Symbol.equality func.Term.symbol ->
                        begin
                          match func.Term.subterms.(0) with
                            | Term.Var _ ->
                                true
                            | _ -> false
                            (*| Term.Const symbol ->
                                not (Symbol.is_fd_symbol symbol)*)

                        end
                    | _ -> false
                end
          end
        then
          acc

        (* redundant, ignore this term. *)
        else if is_subsumed_by_dig term acc then begin
          acc
        end
          
        else begin
          (* get all exceptions of the term which are instances *)
          let instance_exceptions =
            exception_index#find_all_instances ~p_preserving:false term
          in

          (* get all exceptions of the term which can be p-instantiated to instances. *)
          let exceptions =
            (* get all p-unifiables... *)
            let candidate_exceptions =
              exception_index#find_all_unifiable ~p_preserving:false term
            in
              
              (* but keep only the p-instances *)
              List.fold_left
                (fun exceptions candidate_exception ->
                  let unifier =
                    Unification.unify_terms ~recompute:false
                      term 0
                      candidate_exception 1
                  in
                  let instance =
                    Subst.apply_to_term unifier ~insert_db:false term 0
                  in
                    if
                      (* instance must be p-preserving *)
                      Subst.is_p_renaming unifier 1
                        
                      &&
                        
                        (* instance can not be a variant of term,
                           this would mean that term is a p-preserving instance of its exception,
                           which is a contradictory context.
                           thus, this checks for proper instantiation.
                        *)

                        Unification.is_term_instance instance term
                    then
                      instance :: exceptions
                    else
                      exceptions
                )
                instance_exceptions
                candidate_exceptions
          in
            
          (* remove subsumed exceptions. *)
          let pruned_exceptions =
            List.fold_left
              (fun acc current ->
                if is_exception_subsumed current acc then
                  acc
                    
                else
                  current :: subsume_exceptions current acc
              )
              []
              exceptions
          in
            
          (* create the dig *)
          let ig = {
              term = term;
              exceptions = List.sort Term.compare_terms pruned_exceptions;
          }
          in
            (* remove subsumed digs *)
            ig :: subsume_with_ig ig acc
          end
      )
      []
      terms
  in
    





  let dig =
  (* get all igs *)
  index#fold
    (fun acc literal index' ->
       (* ignore negative literals *)
       if not literal.Term.sign then begin
         acc
       end

       (* get all negative elements for this predicate. *)
       else begin
         let exception_index =
           index#find (Term.request_negated_literal ~insert_db:false literal)
         in
         (* all positive instances of the term *)
         let terms =
           index'#fold
             (fun acc term _ ->
               term :: acc
             )
             []
         in
           
         (* check if schema term has to be added *)
         let terms =
           (* assure that the positive schema literal is added for +v *)
           if Config.plus_v context.config then begin
             (* schema already included in positive terms? *)
             if List.exists Term.is_schema_term terms then
               terms
                 
             (* schema already included in negative terms? *)
             else if
               try
                 exception_index#iter
                   (fun term _ ->
                     if Term.is_schema_term term then raise Exit
                   );
                 false
               with
                 | Exit ->
                     true
             then
               terms

             (* nope, so explicitly add the schema term *)
             else begin
               Term.create_schema_term_from_term literal.Term.atom :: terms
             end
           end

           (* nothing to do for -v *)
           else begin
             terms
           end
         in

         (* add this predicate's dig to all digs *)
         let dig =
           assemble_dig terms exception_index
         in
           dig @ acc
         end
         
    )
    []
  in
    if Config.plus_v context.config then
      add_schemas index dig problem
    else
      dig




let print_DIG (context: context) (out: out_channel) (problem: problem) : unit =
  let dig =
    get_dig context problem None
  in
  let dig =
    sort_digs dig
  in
    (* finally print the digs.
       parameters have no business here,
       so use the universal form of the literals *)

    List.iter
      (fun dig ->
         match dig.exceptions with
           | [] ->
               Print.output_line out
                 (Term.term_to_string
                    (Term.request_universal_term dig.term))
                 
           | _ ->
               Print.output_line out
                 (Term.term_to_string
                    (Term.request_universal_term dig.term)
                  ^ " -- exceptions: ");
               List.iter
                 (fun term ->
                    Print.output_line out
                      ("  " ^ Term.term_to_string
                         (Term.request_universal_term term));
                 )
                 dig.exceptions;
      )
      dig







(*** tptp model ***)


  



(* print the finite domain for the given size *)
let tptp_print_finite_domain (out: out_channel) (bound: bound) =
  let domain_size =
    bound#current_bound
  in

  let rec create_enumeration i =
    if i > domain_size then
      []
        
    else
      ("X = " ^ Term.term_to_string (Term.tptp_get_domain_element i)) :: create_enumeration (i + 1)
  in
  let enumeration =
    create_enumeration 1
  in
    Print.output_line out (
        "fof(interpretation_domain, fi_domain,\n"
        (*"*)
      ^ "    ! [X] : ( "
      ^ String.concat " | " enumeration
      ^ " )\n    ).");
    Print.output_line out ""



(* get the dig representation *)
let tptp_get_dig (context: context) (problem: problem)
    (finite_domain: finite_domain option) : (symbol * dig) list =
  let dig =
    get_dig context problem finite_domain
  in
  let dig =
    sort_digs dig
  in

  (* group and sort by symbol *)
  let partition =
    Symbol.SymbolTable.create 1024
  in
    List.iter
      (fun dig ->
        let symbol =
          Term.top_symbol_term dig.term
        in
        let old =
          try
            Symbol.SymbolTable.find partition symbol
          with
            | Not_found ->
                []
        in
          Symbol.SymbolTable.replace partition symbol (dig :: old)
      )
      dig;
    
  let predicates =
    Symbol.SymbolTable.fold
      (fun symbol digs acc -> (symbol, digs) :: acc)
      partition
      []
  in
    List.sort
      (fun (x, _) (y, _) -> Symbol.compare_name x y)
      predicates


(* need to find:
   
   - mapping: generalization -> instance
     p(x, y, z) -> p(a, x', x')
     is
     x -> a, y -> x', z -> x'
   
   - apply reverse mapping to instance,
     using only the first of any bindings to the same variable:
     p(a, y, y)
   
     this ensures the instance is normalized the same way as the generalization
   
   - final mapping: is now x -> a, z -> y

   returns normalized instance and mapping from generalization to it
*)

type mapping = (var * term) list

let map_to_instance (generalization: term) (instance: term) : term * mapping =
  let instantiation =
    Unification.match_terms ~recompute:false
      generalization 0
      instance 1
  in
  let reversed =
    Subst.fold
      (fun acc binding ->
        match binding.Subst.sb_term.Subst.st_term with
          | Term.Var var ->
              (Term.request_var var, Term.request_var binding.Subst.sb_var.Subst.sv_var)
              ::
              acc

          | _ ->
              acc
      )
      []
      instantiation
  in
  let instance' =
    Term.replace_terms_in_term instance reversed
  in
  let instantiation' =
    Unification.match_terms ~recompute:false
      generalization 0
      instance' 1
  in
  let mapping =
    Subst.fold
      (fun acc binding ->
        match binding.Subst.sb_term.Subst.st_term with
          | Term.Var var when
              Var.equal var binding.Subst.sb_var.Subst.sv_var ->
              (* ignore renamings *)
              acc

          | _ ->
              (binding.Subst.sb_var.Subst.sv_var, binding.Subst.sb_term.Subst.st_term)
              ::
              acc
        
      )
      []
      instantiation'
  in
    instance', mapping



(* the formula describing the true values of a predicate in a model.

   for example for the
   ig:     p(x, a) \ p(a, a)

   the formula:
   ![x, y]: p(x, y) <=> ((y = a) & -(x = a))

   with:
   pd_schema: p(x, y)
   ig: p(x, a) to p(x, y) yields: [y = a]
       p(x, a) to p(a, a) yields: [x = a]
       so the only pd_igs element is: [ [y = a], [ [x = a] ] ]
 *)

type predicate_definition = {
  (* the schema term p(x, y, z) *)
  pd_schema: term;

  (* the generalizations with exceptions:
     - the instantiation from the schema term to the generalization
     - the instantiations from the generalization to its exceptions
  *)

  pd_igs: (mapping * mapping list) list;

  (* all instances are false *)
  pd_false: bool;
}




(* represent p(x, y) <=> y = 1 as p(x, 1) *)
let simplify_definition (definition: predicate_definition) =
  match definition.pd_igs with
    | (((var, const) :: []), []) :: [] ->
        (* instantiate exactly one variable of the schema term,
           and no exceptions *)

        let result_var =
          match definition.pd_schema with
            | Term.Func func ->
                let result_term =
                  func.Term.subterms.(Array.length func.Term.subterms - 1)
                in begin
                    match result_term with
                      | Term.Var var -> var
                      | _ -> failwith "Context.simplify_definition 1"
                  end
            | _ -> failwith "Context.simplify_definition 2"
        in
          (* and the instantiated variable is the result variable *)
          if Var.equal var result_var then begin
            let schema_term =
              Term.replace_vars_in_term
                definition.pd_schema
                (fun var' term ->
                  if Var.equal var var' then
                    const
                  else
                    term
                )
            in
              (* recreate the instance and replace the schema with it *)
              { definition with
                pd_schema = schema_term;
                pd_igs = [];
              }
          end

          else
            definition
              
    | _ ->
        definition



(* create the definition for each predicate: *)
let create_predicate_definitions (dig : (symbol * ig list) list) : predicate_definition list =
  List.fold_left
    (fun acc (symbol, dig) ->
      (* create the definition for this dig:
         schema: p(x, y)
         ig:     p(x, a) \ p(a, a)
         --->
         ![x, y]: p(x, y) <=>
         ((y = a) & -(x = a))
      *)

      let schema_term =
        (* this was not in the spirit of the tptp representation,
           which must also specifiy all false terms.
        match dig with
          | ig :: [] ->
              (* only one instance, so make instance to schema term -
                 more compact representation *)
              Term.request_universal_term ig.term
          | _ ->*)

              Term.create_schema_term symbol
      in
      let definitions =
        List.fold_left
          (fun acc ig ->
            let ig_term, mapping =
              map_to_instance schema_term ig.term
            in
            let exceptions =
              List.fold_left
                (fun acc exception_term ->
                  let _exception_term', mapping' =
                    map_to_instance ig_term exception_term
                  in
                    mapping' :: acc
                )
                []
                ig.exceptions
            in
              (mapping, exceptions) :: acc
          )
          []
          dig
      in
      let definition =
        {
          pd_schema = schema_term;
          pd_igs = definitions;
          pd_false = false;
        }
      in
        definition :: acc
      )
    []
    dig




(* create the definition for each function:
   
   as this is for finite domain mode,
   we know from the totality axioms that each function symbol occurs positive
   in the context.
*)

let create_function_definitions (dig : (symbol * ig list) list)
    : predicate_definition list =
  try
    let _, igs =
      List.find
        (fun (symbol, _) ->
          Symbol.equal Symbol.equality symbol
        )
        dig
    in

    (* create for each function *)
    let function_table =
      Symbol.SymbolTable.create 1024
    in
      (* register each term to the function symbol it is an instance of *)
      List.iter
        (fun ig ->
          match ig.term with
            | Term.Func func ->
                begin
                match func.Term.subterms.(0) with
                  | Term.Const symbol
                  | Term.Func { Term.symbol = symbol } ->
                      let old =
                        try
                          Symbol.SymbolTable.find function_table symbol
                        with
                          | Not_found ->
                              []
                      in
                        Symbol.SymbolTable.replace function_table symbol (ig :: old)


                  | Term.Var _ ->
                      (* something like (x = x) *)
                      ()
                end
            | _ ->
                failwith ("Context.create_function_definitions: " ^ Term.term_to_string ig.term)
        )
        igs;

    let function_definitions : predicate_definition list =
      (* create the definitions *)
      Symbol.SymbolTable.fold
        (fun symbol dig acc ->
          (* create the definition for this dig:
             schema: f(x) = y
             ig:     p(x, a) \ p(a, a)
             --->
             ![x, y]: p(x, y) <=>
             ((y = a) & -(x = a))
          *)

          let schema_term =
            (* this was not in the spirit of the tptp representation,
               which must also specifiy all false terms.
            match dig with
              | ig :: [] ->
                  (* only one instance, so make instance to schema term -
                     more compact representation *)
                  Term.request_universal_term ig.term

              | _ ->*)

                  let function_term =
                    Term.create_schema_term symbol
                  in
                  let result_var =
                    Var.create_universal (Symbol.arity symbol)
                  in
                  let result_term =
                    Term.request_var result_var
                  in
                    Term.request_func (Symbol.equality, [| function_term; result_term |])
          in
          let definitions =
            List.fold_left
              (fun acc ig ->
                let ig_term, mapping =
                  map_to_instance schema_term ig.term
                in
                let exceptions =
                  List.fold_left
                    (fun acc exception_term ->
                      let _exception_term', mapping' =
                        map_to_instance ig_term exception_term
                      in
                        mapping' :: acc
                    )
                    []
                    ig.exceptions
                in

                (* empty dig, as the exceptions makes all instances false *)
                let is_empty_dig =
                  (* check didn't pay off, applied almost never *)
                  false
                    (*
                  let vars =
                    Term.vars_of_term ig_term
                  in
                    (* there is a variable *)
                    List.exists
                      (fun var ->
                        (* such that for all domain elements *)
                        List.for_all
                          (fun domain_element ->
                            (* there is an exception *)
                            List.exists
                              (fun exception_mapping' ->
                                match exception_mapping' with
                                  | (var', term) :: [] ->
                                      (* which makes exactly that domain element false *)
                                      Var.equal var var'
                                      &&
                                      Term.term_equal domain_element term 

                                  | _ ->
                                      false
                              )
                              exceptions
                          )
                          domain_elements
                      )
                      vars*)

                in

                  (* exception = schema term, then remove definition.
                     can happen for:
                     r_3(u, v, w)
                     -r_3(f, v, w)
                     where r_3(u, v, w) which is instantiated to r_3(f, v, w)
                     by Finite_domain.relation_to_equations in copy_index *)

                  if List.exists (fun x -> x == []) exceptions then
                    acc
                  else if is_empty_dig then
                    acc
                  else
                    (mapping, exceptions) :: acc
              )
              []
              dig
          in
          let definition =
            {
              pd_schema = schema_term;
              pd_igs = definitions;
              pd_false = false;
            }
          in
            (simplify_definition definition) :: acc
        )
        function_table
        []
    in
      
      (* sort functions by name *)
    let function_symbol_of_definition definition =
      match definition.pd_schema with
        | Term.Func func -> Term.top_symbol_term func.Term.subterms.(0)
        | _ -> failwith "Context.function_symbol_of_definition"
    in

      List.sort
        (fun x y ->
          Symbol.compare_name
            (function_symbol_of_definition x)
            (function_symbol_of_definition y)
        )
        function_definitions;

      
    with
      | Not_found ->
          (* no equality and no functions in the input, I hope. *)
          []






let tptp_print_definitions
    (* print these definitions *)
    (definitions: predicate_definition list)
    (* axiom type, e.g. " *)
    (label: string)
    (out: out_channel)
    : unit =

  Print.output_line out
    ("fof(" ^ label ^ ", (");

  let mapping_to_string mapping : string =
    "( " ^ String.concat " & "
      (List.map
        (fun (var, term) ->
          let left =
            Term.tptp_replace_var var
          and right =
            term
          in
          let term =
            Term.request_func (Symbol.equality, [| left ; right |])
          in
            Term.term_to_string (Term.to_tptp_term term)
        )
        mapping)
      ^ " )"
  in

  (* print the predicates *)
  let definitions' =
    List.fold_left
      (fun acc definition ->
          (* for each generalization *)
          let definitions' =
            List.fold_left
              (fun acc (mapping, mappings') ->
                match mapping, mappings' with
                  | [], [] ->
                      (* all instances are true without exception *)
                      acc
                        
                  | [], _ ->
                      (* generalization is the schema term itself, but there are exceptions *)
                      ("( ~" ^ String.concat " & ~" (List.map mapping_to_string mappings') ^ " )") :: acc
                        
                  | _, [] ->
                      (* instance of schema term without exceptions *)
                      (mapping_to_string mapping) :: acc
                          
                  | _ ->
                      (* instance of schema term with exceptions *)
                      ( "( " ^ mapping_to_string mapping
                      ^
                      " & ~" ^ String.concat " & ~" (List.map mapping_to_string mappings')
                      ^ " )")
                      ::
                      acc
              )
              []
              definition.pd_igs
          in
          let vars =
            List.map Term.tptp_replace_var (List.rev (Term.vars_of_term definition.pd_schema))
          in
          let var_prefix =
            match vars with
              | [] ->
                  "    ( "
              | _ ->
                  "    ( ! [" ^ String.concat ", " (List.map Term.term_to_string vars) ^ "] : "
          in
          let definition' =
            if definition.pd_false then
              var_prefix ^ " ( "
                ^ Term.term_to_string (Term.to_tptp_term definition.pd_schema)
              ^ " <=> $false ) )"
            else
            match definitions' with
              | [] ->
                  (* no exceptions *)
                  var_prefix ^ Term.term_to_string (Term.to_tptp_term definition.pd_schema) ^ " )"
                    
              | _ ->
                  var_prefix  ^ " ( "
                  ^ Term.term_to_string (Term.to_tptp_term definition.pd_schema) ^ " <=> (\n        "
                  ^ String.concat "\n        |\n        " definitions'
                  ^ "\n    ) ) )"
          in
            definition' :: acc
      )
      []
      definitions
  in
    Print.output_line out
      (String.concat "\n    &\n" (List.rev definitions') ^ "\n    ) ).")



let print_tptp_model (context: context) (out: out_channel) (problem: problem)
    (finite_domain: finite_domain option) (bound: bound) : unit =

  begin
    match finite_domain with
      | Some _ -> tptp_print_finite_domain out bound;
      | None -> ()
  end;

  (* get the dig *)
  let dig : (symbol * ig list) list =
    tptp_get_dig context problem finite_domain
  in

  (* create the definition for each predicate *)
  let predicate_definitions : predicate_definition list =
    create_predicate_definitions dig
  in

  (* add predicates of which all instances are false *)
  let predicate_definitions =
    List.fold_left
      (fun acc symbol ->
        if not (Symbol.is_input symbol) then
          acc
            
        (* already defined? *)
        else if
          List.exists
            (fun definition ->
              Symbol.equal symbol (Term.top_symbol_term definition.pd_schema)
            )
            predicate_definitions
        then
          acc

        (* define all instances as false *)
        else begin
          let definition = {
            pd_schema = Term.create_schema_term symbol;
            pd_igs = [];
            pd_false = true;
          }
          in
            definition :: acc
        end
      )
      predicate_definitions
      problem#getPredicateSymbols
  in
  let predicate_definitions =
    List.sort
      (fun x y ->
        Symbol.compare_name
          (Term.top_symbol_term x.pd_schema)
          (Term.top_symbol_term y.pd_schema)
      )
      predicate_definitions
  in

  (* filter equality in finite domain mode *)
  let predicate_definitions =
    if finite_domain != None then
      List.find_all
        (fun definition ->
          not (Symbol.equal Symbol.equality (Term.top_symbol_term definition.pd_schema))
        )
        predicate_definitions
    else
      predicate_definitions
  in

  (* print the definitions *)
  begin
    match finite_domain with
      | None ->
          tptp_print_definitions predicate_definitions
            "interpretation, i_predicates"
            (*"*)
            out;

      | Some _ ->
          let function_definitions =
            create_function_definitions dig
          in

          (* first functions *)
          if List.length function_definitions > 0 then
            tptp_print_definitions function_definitions
              "interpretation_terms, fi_functors" out;
              (*" out;*)

          Print.output_line out "";
          
          (* then predicates *)
          if List.length predicate_definitions > 0 then begin
            tptp_print_definitions predicate_definitions
              "interpretation_atoms, fi_predicates" out;
              (*" out;*)
          end

          (* input better contains no predicates ... *)
          else begin
              (* there is a tptp problem (SYN916-1) which contains only true.
                 so the check below would crash in that case,
                 although everything is fine. *)

              (*
            if problem#containsEquality && List.length problem#getPredicateSymbols = 0 then
              Print.output_line out "
            else
              failwith "
              *)

          end
  end















(*** finite model ***)


let print_multiplication_tables (context: context) (out: out_channel) (problem: problem) (bound: bound) : unit =
  (* print the sorts and domain elements *)
  let domain_size =
    bound#current_bound
  in

  (* find all posible result combinations for a constant/function *)  
  let rec find_result symbol subterms domain_element : int =
    if domain_element > domain_size then begin
      failwith ("print_multiplication_tables: " ^ Symbol.to_string symbol);
    end
            
    else begin
      subterms.(Symbol.arity symbol + 1) <- Finite_domain.get_domain_element domain_element;
      let relation_literal =
        Term.request_literal true
          (Term.request_func (Symbol.get_fd_relation (Symbol.arity symbol + 2), subterms))
      in
        if is_true context relation_literal then
          domain_element
        else
          find_result symbol subterms (domain_element + 1)
    end
  in

  (* build all argument vectors for a function/predicate *)
  let rec enumerate_arguments symbol subterms print position =
    (* done *)
    if position > Symbol.arity symbol then begin
      (* check results *)
      let result =
        find_result symbol subterms 1
      in
        print subterms result
      end
      
    else begin
      (* use each domain element and extend to full argument vector *)
      for i = 1 to domain_size do
        subterms.(position) <- Finite_domain.get_domain_element i;
        enumerate_arguments symbol subterms print (position + 1);
      done;
    end
  in
    
  (* build all argument vectors for a predicate *)
  let rec enumerate_arguments' symbol subterms print position =
    (* done *)
    if position > Symbol.arity symbol then begin
      (* check results *)
      let relation_literal =
        Term.request_literal true
          (Term.request_func (symbol, subterms))
      in
        if is_true context relation_literal then
          print relation_literal
        else
          ()
    end
      
    (* use each domain element and extend to full argument vector *)
    else begin
      for i = 1 to domain_size do
        subterms.(position - 1) <- Finite_domain.get_domain_element i;
        enumerate_arguments' symbol subterms print (position + 1);
      done;
    end
  in

  (* print the multiplication tables *)
  Print.output_line out ("Domain size: " ^ string_of_int domain_size);
  Print.output_line out "";

  (* print the constants *)
  Print.output_line out ("Constants: ");
  List.iter
    (fun symbol ->
      let constant =
        Term.request_const (Symbol.create_fd_symbol symbol)
      in
      let print _subterms result =
        Print.output_line out
          (Term.term_to_string constant ^ " = " ^ Term.term_to_string (Finite_domain.get_domain_element result));
      in
        enumerate_arguments symbol [| constant; constant |] print 1
    )
    (List.sort Symbol.compare_name (List.find_all Symbol.is_input problem#getConstantSymbols));
  Print.output_line out "";
    

  (* print the functions *)
  Print.output_line out ("Functions: ");
  List.iter
    (fun (arity, symbols) ->
      List.iter
        (fun symbol ->
          let symbol_term =
            Term.request_const (Symbol.create_fd_symbol symbol)
          in
          let subterms =
            Array.create (arity + 2) symbol_term
          in
          let print subterms result =
            let subterms' =
              Array.sub subterms 1 (Symbol.arity symbol);
            in
            let function_term =
              Term.request_func (symbol, subterms')
            in
              Print.output_line out
                (Term.term_to_string function_term ^ " = "
                ^ Term.term_to_string (Finite_domain.get_domain_element result));
          in
            enumerate_arguments symbol subterms print 1;
            Print.output_line out "";
        )
        (List.sort Symbol.compare_name (List.find_all Symbol.is_input symbols))
    )
    problem#getFunctionArities;
  Print.output_line out "";

  (* print the predicates *)
  Print.output_line out ("Predicates: ");
  List.iter
    (fun (arity, symbols) ->
      List.iter
        (fun symbol ->
          Print.output_line out (Symbol.to_string symbol ^ ":");
          let subterms =
            Array.create arity (Term.request_var (Var.create_parametric 0))
          in
          let print literal =
            Print.output_line out (Term.term_to_string literal.Term.atom)
          in
            enumerate_arguments' symbol subterms print 1;
            Print.output_line out "";
        )
        (List.sort Symbol.compare_name (List.find_all Symbol.is_input symbols))
    )
    problem#getPredicateArities