module type S =
sig
  type t

  val any: t
  val empty: t
  val cup: t -> t -> t
  val cap: t -> t -> t
  val diff: t -> t -> t
  val is_empty: t -> bool
end

type 'a bool = ('a list * 'a list) list

module Make(X1 : S)(X2 : S) =
struct
  type t = (X1.t * X2.t) list

(* Possible optimizations:
   - check whether t1 or t2 is empty initially
   - check s1 = t1 (structural equility)
*)
  let rec add root t1 t2 = function
    | [] -> (t1,t2) :: root
    | (s1,s2) :: rem ->
	let i = X1.cap t1 s1 in
	if X1.is_empty i then add ((s1,s2)::root) t1 t2 rem
	else (
	  let root = (i, X2.cup t2 s2) :: root in
	  let k = X1.diff s1 t1 in
	  let root = if not (X1.is_empty k) then (k, s2) :: root else root in
	  let j = X1.diff t1 s1 in
	  if not (X1.is_empty j) 
	  then add root j t2 rem 
	  else List.rev_append root rem
	)

  let normal x =
    List.fold_left (fun accu (t1,t2) -> add [] t1 t2 accu) [] x

  let rec bigcap_aux t1 t2 = function
    | (s1,s2)::rem -> bigcap_aux (X1.cap t1 s1) (X2.cap t2 s2) rem
    | [] -> (t1,t2)
  let bigcap = bigcap_aux X1.any X2.any

(* optimize: one knows that the t1 are pairwise disjoint ... *)
  let line accu (p,n) =
    let (d1,d2) = bigcap p in
    if not ((X1.is_empty d1) || (X2.is_empty d2)) then
      (let resid = ref X1.empty in
       let accu = List.fold_left
	 (fun accu (t1,t2) ->
	    let i = X1.cap d1 t1 in
	    if not (X1.is_empty i) then
	      (resid := X1.cup !resid t1;
	       let t2 = X2.diff d2 t2 in
	       if not (X2.is_empty t2) then add [] i t2 accu else accu
	      ) else accu
	 ) accu (normal n) in
       let d1 = X1.diff d1 !resid in
       if not (X1.is_empty d1) then add [] d1 d2 accu else accu)
    else accu

  (* Merge t1's with same t2 *)
  let equiv t s = X2.is_empty (X2.diff t s) && X2.is_empty (X2.diff s t)
  let cleanup l =
    let rec aux accu (t1,t2) =
      match accu with
	| [] -> [ (t1,t2) ]
	| (s1,s2) :: rem when equiv t2 s2 -> (X1.cup s1 t1, s2) :: rem
	| (s1,s2) :: rem -> (s1,s2) :: (aux rem (t1,t2)) in
    List.fold_left aux [] l
      
  let boolean_normal x =
    cleanup (List.fold_left line [] x)

  let boolean x =
    List.fold_left (fun accu x -> (line [] x) @ accu) [] x

  let pi1 =
    List.fold_left (fun accu (t1,t2) -> X1.cup accu t1) X1.empty

  let pi2 =
    List.fold_left (fun accu (t1,t2) -> X2.cup accu t2) X2.empty

  let pi2_restricted restr = 
    List.fold_left (fun accu (t1,t2) -> 
		      if X1.is_empty (X1.cap t1 restr) then accu 
		      else X2.cup accu t2) X2.empty
end

