module type F_PROBLEM = sig
  type state
  type move

  val success : state -> bool
  val moves : state -> (move * state) list

  type table

  val create : unit -> table
  val add : table -> state -> unit
  val mem : table -> state -> bool
  val clear : table -> unit
end

module type M_PROBLEM = sig
  type move

  val success : unit -> bool
  val moves : unit -> move list
  val do_move : move -> unit
  val undo_move : move -> unit
  val add : unit -> unit
  val mem : unit -> bool
  val clear : unit -> unit
end

module F (Q : Ke.Sigs.M) (P : F_PROBLEM) = struct
  let search s0 =
    let visited = P.create () in
    let already s =
      P.mem visited s
      ||
      (P.add visited s;
       false)
    in
    let _ = already s0 in
    let q = Q.create () in
    Q.push q ([], s0);
    let rec bfs () =
      if Q.is_empty q then raise Not_found;
      let path, s = Q.pop_exn q in
      if P.success s then (s, List.rev path)
      else (
        List.iter
          (fun (m, s') -> if not (already s') then Q.push q (m :: path, s'))
          (P.moves s);
        bfs ())
    in
    bfs ()
end

module M (Q : Ke.Sigs.M) (P : M_PROBLEM) = struct
  let rec cut_head n l = if n == 0 then l else cut_head (pred n) (List.tl l)

  let common_psuffix (n1, l1) (n2, l2) =
    let rec suffix l1 l2 =
      if l1 == l2 then l1 else suffix (List.tl l1) (List.tl l2)
    in
    if n1 < n2 then suffix l1 (cut_head (n2 - n1) l2)
    else if n2 < n1 then suffix (cut_head (n1 - n2) l1) l2
    else suffix l1 l2

  let search () =
    let already () =
      P.mem ()
      ||
      (P.add ();
       false)
    in
    let q = Q.create () in
    Q.push q (0, []);
    let cpath = ref (0, []) in
    let rec restore_state path =
      let suf = common_psuffix path !cpath in
      let rec backward = function
        | m :: r as p when p != suf ->
            P.undo_move m;
            backward r
        | _ -> ()
      in
      let rec forward = function
        | m :: r as p when p != suf ->
            forward r;
            P.do_move m
        | _ -> ()
      in
      backward (snd !cpath);
      forward (snd path);
      cpath := path
    in
    let rec bfs () =
      if Q.is_empty q then raise Not_found;
      let ((n, path) as s) = Q.pop_exn q in
      restore_state s;
      if P.success () then List.rev path
      else if not (already ()) then (
        List.iter (fun m -> Q.push q (succ n, m :: path)) (P.moves ());
        bfs ())
      else bfs ()
    in
    bfs ()
end
