open Global

open Formula

type term_template = {freevariables: Formula.variable_list;
                      term    : exprtype}

type array_of_arrays = { mutable size :int;
                         mutable mark_point : int;
                        mutable slots: term_id array array}

let term_template_to_string template = 
    let freevar_str=Formula.variable_list_to_string template.freevariables in
    let expr_str = Formula.debugging_exprtype_to_string template.term in
    "{freevars="^freevar_str^";term="^expr_str^"}"


exception Referenced_undefined_term
exception Empty_term_database
exception Term_removal_error
exception Ground_term_added_as_template
exception Pred_added_as_term_template


let term_array_size = 100

let term_table_size = 100

class term_db =
  let term_table = (Hashtbl.create term_table_size:(Global.term_id,bool)Hashtbl.t) in
  let term_id_arrays_table = 
    (Hashtbl.create 5: (int,array_of_arrays)Hashtbl.t) 
  in

object (self)
  initializer Random.self_init()
  val mutable term_db_updated_since_reg_enabled = false
  val mutable templates: term_template list = []

  val mutable term_registration_enabled = true
  
  method get_templates = templates
  
  method term_db_has_been_updated_since_registration_enabled = term_db_updated_since_reg_enabled
  
  method add_term_sort sort_id =   
        let (term_id_arrays:array_of_arrays) =
            { size = 0 ; slots=Array.make 1 (Array.make term_array_size 0); mark_point = 0}
        in
        Hashtbl.add term_id_arrays_table sort_id term_id_arrays


  method add_term sort_id (term_id:term_id) =
    if term_registration_enabled then
    begin
      let term_id_arrays=Hashtbl.find term_id_arrays_table sort_id in
      if not (Hashtbl.mem term_table term_id) then
        begin
          let row=term_id_arrays.size / term_array_size in
          let col=term_id_arrays.size mod term_array_size in
          if(row>(Array.length term_id_arrays.slots)-1) then
            (term_id_arrays.slots<-
              (Array.append term_id_arrays.slots (Array.make 1 (Array.make
              term_array_size term_id)));
              ())
           else
             (
              term_id_arrays.slots.(row).(col)<- term_id;
              ());
          (*self#add_recent_term_sort sort_id;*)
          term_id_arrays.size <- term_id_arrays.size+1;
          Hashtbl.add term_table term_id true;
          term_db_updated_since_reg_enabled <- true (* states that the term_db has been updated since the last time that term registration was enabled *)
        end
    end

  method get_largest_term_db_size = 
    Hashtbl.fold (fun key term_id_array max -> 
      if term_id_array.size>max then term_id_array.size else max) 
                 term_id_arrays_table 0

  method get_term_db_size sort_id = 
    let term_id_arrays=Hashtbl.find term_id_arrays_table sort_id in
      term_id_arrays.size
      
  method get_term_id sort_id index = 
    let term_id_arrays=Hashtbl.find term_id_arrays_table sort_id in
      if term_id_arrays.size=0 then raise Empty_term_database;
      if term_id_arrays.size<=index then raise Referenced_undefined_term;
        term_id_arrays.slots.(index / term_array_size).(index mod 
          term_array_size)

  method get_random_term sort_id =
    let term_id_arrays=Hashtbl.find term_id_arrays_table sort_id in
      if term_id_arrays.size=0 then raise Empty_term_database;
      let rand=Random.int (term_id_arrays.size) in
        term_id_arrays.slots.(rand / term_array_size).(rand mod term_array_size)

  method remove_term sort_id = 
    try 
      let term_id_arrays=Hashtbl.find term_id_arrays_table sort_id in
      if term_id_arrays.size < 2 then
        begin
          term_id_arrays.size <- 0;
          term_id_arrays.slots <- Array.make 1 (Array.make term_array_size 0)
        end
      else if term_id_arrays.size = term_array_size + 1 then
        begin
          term_id_arrays.slots <- 
            Array.sub (term_id_arrays.slots)
              0 ((Array.length term_id_arrays.slots) - 1)
        end;
      term_id_arrays.size <- term_id_arrays.size - 1;
      if term_id_arrays.mark_point > term_id_arrays.size then term_id_arrays.mark_point <- term_id_arrays.size
   with _ -> raise Term_removal_error

  method add_template nonground_term =
(* takes a non ground term and adds it to the template list *)
    if Formula.is_term nonground_term then
      match nonground_term with
        E(None,{freevars=freelist},_,_,_) -> 
          let new_template={freevariables = freelist; term = nonground_term} in
            templates<- new_template::(templates)
      | E(Some _,_,_,_,_)-> raise Ground_term_added_as_template
    else
      raise Pred_added_as_term_template
      
  method clear =
    Hashtbl.iter (fun _ y ->
      y.size <- 0;
      y.slots <- Array.make 1 (Array.make term_array_size 0)
    ) term_id_arrays_table;
    Hashtbl.clear term_table;

  method enable_term_registration = 
      term_db_updated_since_reg_enabled<- false; 
      term_registration_enabled <- true
  
  method disable_term_registration = term_registration_enabled <- false

  method mark_new_terms = Hashtbl.iter (fun _ term_id_arrays -> term_id_arrays.mark_point<- term_id_arrays.size) term_id_arrays_table 
  
  method get_first_new_term_position sort_id =   
    let term_id_arrays=Hashtbl.find term_id_arrays_table sort_id in
      term_id_arrays.mark_point

  method print_templates = 
              Printf.printf "\ntemplate list:\n";
              List.iter ( fun template -> Printf.printf "%s\n" (term_template_to_string template) ) templates;
              Printf.printf "template list done:\n"
              
  method print_term_db =
              Printf.printf "\nterm_db:\n";
              Hashtbl.iter (fun sort_id array_of_arrays ->
                                  Printf.printf "type_id: %d size=%d mark_point=%d\n" sort_id (array_of_arrays.size) (array_of_arrays.mark_point);
                                  Array.fold_left (fun slot_num array -> 
                                                      Printf.printf " slot %d\n" slot_num;
                                                      Array.fold_left (fun term_index term_id -> 
                                                                          Printf.printf "  index: %d id: %d\n" term_index term_id;
                                                                          term_index+1
                                                                      ) (slot_num*term_array_size) array;
                                                      (slot_num + 1)
                                                  ) 0 (array_of_arrays.slots);
                                  ()
                           ) term_id_arrays_table;
              Printf.printf "term_db done\n"
end

let term_db=new term_db;;