module Common = struct
  (* All of these values are the same regardless of the instantiation of the
     functor. That is to say, they're independent of the representation of
     elements *)

  let empty = 0
  let union = ( lor )
  let inter = ( land )
  let equal = Int.equal
  let compare = Int.compare
end

module Make (Element : sig
    type t

    val to_int : t -> int
    val all : t list
    val to_dyn : t -> Dyn.t
  end) =
struct
  type t = int

  include Common

  let () =
    assert (List.length Element.all < Sys.int_size);
    let (_ : unit Int.Map.t) =
      Int.Map.of_list_map_exn Element.all ~f:(fun x ->
        let x = Element.to_int x in
        assert (x >= 0 && x < Sys.int_size);
        x, ())
    in
    ()
  ;;

  let singleton x = 1 lsl Element.to_int x
  let add t x = union t (singleton x)
  let mem t x = t land singleton x <> empty

  let to_dyn t : Dyn.t =
    Set
      (List.fold_left Element.all ~init:[] ~f:(fun acc x ->
         if mem t x then Element.to_dyn x :: acc else acc))
  ;;

  let of_func =
    let all = Array.of_list Element.all in
    fun f ->
      let acc = ref empty in
      for i = 0 to Array.length all - 1 do
        if f all.(i) then acc := union !acc (singleton all.(i))
      done;
      !acc
  ;;

  let of_list = List.fold_left ~init:empty ~f:add
end
