with Traversal.Threaded_Display;

package body Search_Team is

   ---------------------
   -- Launch_Searcher --
   ---------------------

   procedure Launch_Searcher
     (Starting_Point : in     Maze.Position;
      Track          : in     Traversal.Trail;
      Launched       :    out Boolean)
   is
      Member : Volunteer;
   begin
      Member := Pool.Next_Member (Wait => False);
      if Member /= null then
         Member.Start (Member, Starting_Point, Track);
         Launched := True;
      else
         Launched := False;
      end if;
   end Launch_Searcher;

   --------------
   -- Delegate --
   --------------

   procedure Delegate
      (Next    : in     Maze.Positions;
       History : in     Traversal.Trail;
       Store   : in out Search_Leads.Repository)
   is
      Success : Boolean;
   begin
      for Move in Next'Range loop
         Launch_Searcher (Starting_Point => Next (Move),
                          Track          => History,
                          Launched       => Success);
         if not Success then  --  none available now
            Search_Leads.Save (Store, Next (Move), History);
         end if;
      end loop;
   end Delegate;

   --------------
   -- Searcher --
   --------------

   task body Searcher is
      Path             : Traversal.Trail;
      The_Maze         : constant Maze.Reference := Maze.Instance;
      Current_Position : Maze.Position;
      Myself           : Volunteer;
      Unsearched       : Search_Leads.Repository;
      Candidates       : Maze.Positions (Maze.Possible_Moves);
      Num_Candidates   : Maze.Moves;

      use type Maze.Moves;
   begin
      loop
         select
            accept Start (My_ID : in Volunteer;
                          Start : in Maze.Position;
                          Track : in Traversal.Trail)
            do
               Myself := My_ID;
               Current_Position := Start;
               Path := Track;
            end Start;
         or
            terminate;
         end select;
         Searching : loop
            Pursue_Lead : loop
               --  Mark the Current_Position on the Path within The_Maze, and
               --  determine any candidate positions we can move to next, from
               --  the Current_Position.
               Traversal.Mark
                 (Current_Position, Path, The_Maze, Candidates, Num_Candidates);
               exit Pursue_Lead when
                  Maze.At_Exit (The_Maze, Current_Position)
                  or
                  Num_Candidates = 0;
               --  Attempt to send other searchers off searching these
               --  candidates, except for the last one. This will be a no-op if
               --  there is only one candidate position. Note that delegation
               --  will fail if the pool is exhausted, in which case unassigned
               --  candidates are put into the Unsearched search lead
               --  repository.
               Delegate (Candidates (1 .. Num_Candidates - 1), Path, Unsearched);
               --  We pursue the last (or only) candidate ourselves.
               Current_Position := Candidates (Num_Candidates);
            end loop Pursue_Lead;
            --  now there is no further position we can go to from
            --  Current_Position
            if Maze.At_Exit (The_Maze, Current_Position) then
               Traversal.Threaded_Display.Show (Path, On => The_Maze);
            end if;
            exit Searching when Search_Leads.Empty (Unsearched);
            --  There are remaining search leads that we could not delegate so
            --  we restore our state to the point that the lead was discovered
            --  and search from there.
            Search_Leads.Restore (Unsearched, Current_Position, Path);
         end loop Searching;
         -- nowhere else to search so we go back to the pool
         Pool.Return_Member (Myself);
      end loop;
   end Searcher;

--  Load the pool, during package elaboration, with the maximum number of
--  searchers specified.
begin
   Pool.Load (Pool.Initial_Values'(others => new Searcher));
end Search_Team;

