(*  File: utils.ml

    AIFAD - Automated Induction of Functions over Algebraic Datatypes

    Author: Markus Mottl
    email:  markus.mottl@gmail.com
    WWW:    http://www.ocaml.info

    Copyright (C) 2002  Austrian Research Institute for Artificial Intelligence
    Copyright (C) 2003- Markus Mottl

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

(* $Id: utils.ml,v 1.59 2006/01/17 00:23:39 mottl Exp $ *)

(* Integer sets and maps *)

module IntSet = Set.Make (struct type t = int let compare = compare end)
module IntMap = Map.Make (struct type t = int let compare = compare end)


(* Option functions *)

let unlift_opt = function Some x -> x | None -> failwith "unlift_opt"
let unlift_opts opts = Array.map unlift_opt opts


(* Either *)

type ('a, 'b) either = Left of 'a | Right of 'b


(* One_list *)

type 'a one_list = OneEl of 'a | OneCons of 'a * 'a one_list

let rec one_list_rev_loop acc = function
  | OneCons (el, one_lst) -> one_list_rev_loop (OneCons (el, acc)) one_lst
  | OneEl el -> OneCons (el, acc)

let one_list_rev = function
  | OneCons (el, one_lst) -> one_list_rev_loop (OneEl el) one_lst
  | one_lst -> one_lst

let rec one_list_fold_left f acc = function
  | OneEl el -> f acc el
  | OneCons (el, one_lst) -> one_list_fold_left f (f acc el) one_lst


(* List functions *)

let rec list_iteri_loop f i = function
  | [] -> ()
  | h :: t -> f i h; list_iteri_loop f (i + 1) t

let list_iteri f lst = list_iteri_loop f 0 lst

let rec list_fold_lefti_loop f ix acc = function
  | [] -> acc
  | h :: t -> list_fold_lefti_loop f (ix + 1) (f ix acc h) t

let list_fold_lefti f acc lst = list_fold_lefti_loop f 0 acc lst

let list_to_rev_array n = function
  | [] -> [||]
  | h :: t ->
      let ar = Array.make n h in
      let rec loop ix = function
        | h :: t ->
            Array.unsafe_set ar ix h;
            if ix >= 0 then loop (ix - 1) t
            else ar
        | [] -> assert false in
      loop (n - 1) t


(* Array functions *)

let array_is_empty ar = Array.length ar = 0

let rec array_of_nlist_loop ar lix ix lst =
  if ix > lix then ar
  else
    match lst with
    | [] -> failwith "array_of_nlist_loop: list too short"
    | h :: t ->
        Array.unsafe_set ar ix h;
        array_of_nlist_loop ar lix (ix + 1) t

let array_of_nlist n = function
  | [] -> [||]
  | h :: t -> array_of_nlist_loop (Array.make n h) (n - 1) 1 t

let rec array_map_of_nlist_loop f ar lix ix lst =
  if ix > lix then ar
  else
    match lst with
    | [] -> failwith "array_map_of_nlist_loop: list too short"
    | h :: t ->
        Array.unsafe_set ar ix (f h);
        array_map_of_nlist_loop f ar lix (ix + 1) t

let array_map_of_nlist f n = function
  | [] -> [||]
  | h :: t -> array_map_of_nlist_loop f (Array.make n (f h)) (n - 1) 1 t

let rec array_of_rev_list_loop ar ix = function
  | [] -> ar
  | h :: t -> Array.unsafe_set ar ix h; array_of_rev_list_loop ar (ix - 1) t

let array_of_rev_list = function
  | [] -> [||]
  | h :: t ->
      let len = List.length t in
      array_of_rev_list_loop (Array.make (len + 1) h) (len - 1) t

let array_of_nrev_list n = function
  | [] -> [||]
  | h :: t -> array_of_rev_list_loop (Array.make n h) (n - 1) t

let rec array_map_of_rev_list_loop f ar ix = function
  | [] -> ar
  | h :: t ->
      Array.unsafe_set ar ix (f h);
      array_map_of_rev_list_loop f ar (ix - 1) t

let array_map_of_rev_list f = function
  | [] -> [||]
  | h :: t ->
      let len = List.length t in
      array_map_of_rev_list_loop f (Array.make (len + 1) (f h)) (len - 1) t

let lcons lst el = el :: lst
let array_to_rev_list ar = Array.fold_left lcons [] ar

let array_map_to_rev_list f ar =
  let coll lst el = f el :: lst in
  Array.fold_left coll [] ar

let array_rev_fold_left f acc ar =
  let acc_ref = ref acc in
  for i = Array.length ar - 1 downto 0 do
    acc_ref := f !acc_ref (Array.unsafe_get ar i)
  done;
  !acc_ref

let array_map_to_list f ar =
  let coll lst el = f el :: lst in
  array_rev_fold_left coll [] ar

let array_rev_iter f ar =
  for i = Array.length ar - 1 downto 0 do f (Array.unsafe_get ar i) done

let array_map2 f ar =
  let len = Array.length ar in
  if len = 0 then [||], [||]
  else (
    let a, b = f (Array.unsafe_get ar 0) in
    let res1 = Array.make len a in
    let res2 = Array.make len b in
    for i = 1 to len - 1 do
      let a, b = f (Array.unsafe_get ar i) in
      Array.unsafe_set res1 i a;
      Array.unsafe_set res2 i b;
    done;
    res1, res2)

let array_mapi2 f ar =
  let len = Array.length ar in
  if len = 0 then [||], [||]
  else (
    let a, b = f 0 (Array.unsafe_get ar 0) in
    let res1 = Array.make len a in
    let res2 = Array.make len b in
    for i = 1 to len - 1 do
      let a, b = f i (Array.unsafe_get ar i) in
      Array.unsafe_set res1 i a;
      Array.unsafe_set res2 i b;
    done;
    res1, res2)

let rec array_forall_loop ix len p ar =
  ix = len || p (Array.unsafe_get ar ix) && array_forall_loop (ix + 1) len p ar

let array_forall p ar = array_forall_loop 0 (Array.length ar) p ar

let array_filter p ar =
  let els_ref = ref [] in
  let cnt_ref = ref 0 in
  for i = 0 to Array.length ar - 1 do
    let el = Array.unsafe_get ar i in
    if p el then (incr cnt_ref; els_ref := el :: !els_ref)
  done;
  let cnt = !cnt_ref in
  if cnt = 0 then [||]
  else
    match !els_ref with
    | h :: t ->
        let res = Array.make cnt h in
        array_of_rev_list_loop res (cnt - 1) t
    | [] -> assert false

let array_iter1 f ar =
  for i = 1 to Array.length ar - 1 do f (Array.unsafe_get ar i) done

let rec array_fold_left_loop ix len f acc ar =
  if ix = len then acc
  else
    let new_acc = f acc (Array.unsafe_get ar ix) in
    array_fold_left_loop (ix + 1) len f new_acc ar

let array_fold_left1 f acc ar =
  array_fold_left_loop 1 (Array.length ar) f acc ar

let rec array_fold_lefti_loop ix len f acc ar =
  if ix = len then acc
  else
    let new_acc = f ix acc (Array.unsafe_get ar ix) in
    array_fold_lefti_loop (ix + 1) len f new_acc ar

let array_fold_lefti f acc ar =
  array_fold_lefti_loop 0 (Array.length ar) f acc ar

let array_fold_left1i f acc ar =
  array_fold_lefti_loop 1 (Array.length ar) f acc ar

let rec array_fold_righti_loop ix f ar acc =
  if ix < 0 then acc
  else
    let new_acc = f ix (Array.unsafe_get ar ix) acc in
    array_fold_righti_loop (ix - 1) f ar new_acc

let array_fold_righti f ar acc =
  array_fold_righti_loop (Array.length ar - 1) f ar acc

let rec array_find_index_loop ix p ar len =
  if ix = len then raise Not_found
  else if p (Array.unsafe_get ar ix) then ix
  else array_find_index_loop (ix + 1) p ar len

let array_find_index p ar = array_find_index_loop 0 p ar (Array.length ar)

let transpose_matrix m =
  let z = Array.length m in
  if z = 0 then [||]
  else
    let m0 = Array.unsafe_get m 0 in
    let s = Array.length m0 in
    if s = 0 then Array.make s [||]
    else (
      let m00 = Array.unsafe_get m0 0 in
      let new_m = Array.make_matrix s z m00 in
      let z_1 = z - 1 in
      for i = 0 to s - 1 do
        let ar = Array.unsafe_get new_m i in
        for j = 0 to z_1 do
          Array.unsafe_set ar j (Array.unsafe_get (Array.unsafe_get m j) i)
        done
      done;
      new_m)


(* Miscellaneous functions *)

let id x = x

let cmp_fst (x1, _) (x2, _) = compare x1 x2
let cmp_snd (_, y1) (_, y2) = compare y1 y2
let make_pair a b = a, b

let unwind_protect f fx f_exit fx_exit =
  let res = try f fx with exc -> f_exit fx_exit; raise exc in
  f_exit fx_exit;
  res

let do_open_in name f =
  let ic = open_in name in
  unwind_protect f ic close_in ic

let do_open_out name f =
  let oc = open_out name in
  unwind_protect f oc close_out oc
