open Ident

type t = Types.t

let rec try_seq f = function
  | [] -> raise Not_found
  | hd::tl -> try f hd with Not_found -> try_seq f tl

module D = Set.Make(Types)
  
let absent = Types.cons (Types.Record.or_absent Types.empty)

let rec get memo t =
  if D.mem t memo then raise Not_found;
  let memo = D.add t memo in
  let cons t = Types.cons (get memo t) in
  let pair (t1,t2) = Types.times (cons t1) (cons t2) in
  let xml (t1,t2) = Types.xml (cons t1) (cons t2) in
  let fields = function
    | (true,_) -> assert false (* absent *)
    | (false,t) -> cons t in
  let record (r,some,none) = 
    let r = LabelMap.filter (fun l (o,t) -> not o) r in
    Types.record_fields (not none, LabelMap.map fields r) in
  let typ u = 
    let u = Types.cap t u in 
    if Types.is_empty u then raise Not_found else u in
  try try_seq typ [ Types.Int.any; Types.Atom.any; Types.Char.any ] with Not_found ->
  try try_seq pair (Types.Product.get t) with Not_found ->
  try try_seq xml (Types.Product.get ~kind:`XML t) with Not_found ->
  try 
    let r = Types.Record.get t in
    let r = List.sort (fun (_,_,n1) (_,_,n2) -> -(compare n1 n2)) r in
    try_seq record r with Not_found ->
  try Types.Arrow.sample t with Not_found -> raise Not_found
(* t *)
(*  raise Not_found *)

     
let get = get D.empty

let print = Types.Print.print


let try_single r f x =
  try
    let v = f x in
    match !r with
      | None -> r := Some v
      | Some v' -> if (Types.Const.compare v v' !=0) then raise Exit
  with Not_found -> ()

let rec single memo t =
  if D.mem t memo then raise Exit;
  let memo = D.add t memo in
  let pair (t1,t2) = Types.Pair (single memo t1, single memo t2) in
  let xml (t1,t2) = Types.Xml (single memo t1, single memo t2) in
  let int t = Types.Integer (Intervals.single (Types.Int.get t)) in
  let atom t = Types.Atom (Atoms.single (Types.Atom.get t)) in
  let char t = Types.Char (Chars.single (Types.Char.get t)) in
  let fields = function
    | (true,_) -> assert false
    | (false,t) -> single memo t in
  let record = function
    | (r,false,true) -> 
	let r = 
	  LabelMap.filter 
	    (fun l (o,t) -> 
	       if o then if (Types.non_empty t) then raise Exit else false
	       else true) r in
	Types.Record (LabelMap.map fields r)
    | _ -> raise Exit in
  let r = ref None in
  try_single r int t;
  try_single r char t;
  try_single r atom t;
  List.iter (try_single r pair) (Types.Product.get t);
  List.iter (try_single r xml) (Types.Product.get ~kind:`XML t);
  List.iter (try_single r record) (Types.Record.get t);
  (try ignore (Types.Arrow.sample t); raise Exit with Not_found -> ());
  match !r with
    | None -> raise Not_found
    | Some c -> c

let single = single D.empty

let single_opt t =
  try Some (single t)
  with Not_found | Exit -> None
