let e_bar_cache = ref [||]

(* return log of tail probability mass *)	
let get_e_bar lower_bound delta error_log_counts =
  let len = Array.length error_log_counts in
  let ret = 
    if Array.length !e_bar_cache <> len then Array.init len (fun i -> Search.monotone_increasing (lower_bound i) delta 0. 1.) 
    else !e_bar_cache in
  e_bar_cache := Array.copy ret;
  ret
    
(* 
   O(lower * error_log_counts + train_examples) uncached

   O(lower) = O(1) (agnostic/relative_entropy/cached binomial)
              O(n^.5) (binomial uncached)
 *)
let thresh = 700

let computable_shell lower_bound log_delta error_log_counts train_examples train_err p =
  let error_log_counts = Array.copy error_log_counts in
  error_log_counts.(train_err) <- Log_float.add error_log_counts.(train_err) (log 1.);
  let sum = ref Log_float.zero in
  let new_delta = log_delta -. log 2. in
  let e_bar = get_e_bar lower_bound new_delta error_log_counts in
  let e_bar = Array.map (fun x -> max p x) e_bar in
  let p_hat = ref Log_float.zero in

  if train_examples < thresh then
    for i = 0 to train_examples do
      if error_log_counts.(i) > Log_float.zero then
	let projection_size = error_log_counts.(i) +. Binomial.log_binom_coeff train_examples e_bar.(i) train_err in
	p_hat := Log_float.add !p_hat projection_size
    done
  else
    for i = 0 to train_examples do
      if error_log_counts.(i) > Log_float.zero then
	let projection_size = Relative_entropy.relative_entropy_upper error_log_counts.(i) train_examples train_err e_bar.(i) in
	p_hat := Log_float.add !p_hat projection_size
    done;
  min (!p_hat +. log 2. +. log 2. +. log (float train_examples)) 0.
 
exception Error of string

(* 
   O(upper * num_examples) 
   
*)
let bound_sample upper_bound hypothesis_error_counts log_delta log_hypothesis_size = 
  let num_log_counts = Array.fold_left (fun x y -> Log_float.add x y) Log_float.zero hypothesis_error_counts in
  let log_max = log (float max_int) in
  if num_log_counts > log_hypothesis_size then print_string "warning: more samples than hypotheses'n";

  let tot = exp num_log_counts in
  let num_samples = int_of_float tot in
  let log_delta = log_delta -. log (float (Array.length hypothesis_error_counts)) in
  
  let cumulative = Array.copy hypothesis_error_counts in
  let log_counts = ref Log_float.zero in
  for i = 0 to Array.length hypothesis_error_counts - 1 do
    let new_counts = Log_float.add !log_counts hypothesis_error_counts.(i) in
    cumulative.(i) <- new_counts;
    log_counts := new_counts;
  done;
  let bound_cumulative = 
    if log_max < num_log_counts 
    then Array.map (fun log_count -> 
      let count = int_of_float (exp log_count) in
      exp (Log_float.add (log_count -. num_log_counts) ( (~-. log_delta -. (log 2. +. num_log_counts)) /. 2.))
		   ) cumulative
    else Array.map (fun log_count -> 
      let count = int_of_float (exp log_count) in
      Search.monotone_increasing (upper_bound 0. num_samples count) log_delta 0. 1.) cumulative  in
  
  let ret = Array.copy hypothesis_error_counts in
  let counts = ref 0. in
  for i = 0 to Array.length hypothesis_error_counts - 1 do
    let diff = bound_cumulative.(i) -. !counts in
    ret.(i) <- Log_float.add (log diff) log_hypothesis_size;
    counts := bound_cumulative.(i);
  done;
  ret
    

