builder :: Options -> TVar Queue -> IO ()
builder opts queueTVar = builder' []
  where
    builder' :: [MVar ()] -> IO ()
    builder' mvars = do
      mJob   <- takeNextJob queueTVar
      mvars' <- case mJob of
        Nothing  -> do
          threadDelay $ 1000 * 1000
          return mvars
        Just job -> builder'' job mvars
      builder' mvars'

    builder'' :: Job -> [MVar ()] -> IO [MVar ()]
    builder'' job mvars = do
      mvars' <- cleanMVars mvars
      if length mvars' >= optThreads opts
        then do
          threadDelay $ 5 * 1000 * 1000
          builder'' job mvars'
        else do
          mvar' <- newEmptyMVar
          _ <- forkIO $ buildNext opts job mvar'
          return $ mvar' : mvars'

    cleanMVars :: [MVar ()] -> IO [MVar ()]
    cleanMVars mvars = do
      mMVars <- forM mvars $ \mvar -> do
        mres <- tryTakeMVar mvar
        return $ maybe (Just mvar) (const Nothing) mres
      return $ catMaybes mMVars
builder :: Options -> TVar Queue -> IO ()
builder opts queueTVar = builder' []
  where
    builder' :: [MVar ()] -> IO ()
    builder' mvars = do
      mJob   <- takeNextJob queueTVar
      mvars' <- case mJob of
        Nothing  -> do
          threadDelay $ 1000 * 1000
          return mvars
        Just job -> builder'' job mvars
      builder' mvars'

    builder'' :: Job -> [MVar ()] -> IO [MVar ()]
    builder'' job mvars = do
      mvars' <- cleanMVars mvars
      if length mvars' >= optThreads opts
        then do
          threadDelay $ 5 * 1000 * 1000
          builder'' job mvars'
        else do
          mvar' <- newEmptyMVar
          _ <- forkIO $ buildNext opts job mvar'
          return $ mvar' : mvars'

    cleanMVars :: [MVar ()] -> IO [MVar ()]
    cleanMVars mvars = do
      mMVars <- forM mvars $ \mvar -> do
        mres <- tryTakeMVar mvar
        return $ maybe (Just mvar) (const Nothing) mres
      return $ catMaybes mMVars
builder opts queueTVar = builder' []
  where
    builder' :: [MVar ()] -> IO ()
    builder' mvars = do
      mJob   <- takeNextJob queueTVar
      mvars' <- case mJob of
        Nothing  -> do
          threadDelay $ 1000 * 1000
          return mvars
        Just job -> builder'' job mvars
      builder' mvars'

    builder'' :: Job -> [MVar ()] -> IO [MVar ()]
    builder'' job mvars = do
      mvars' <- cleanMVars mvars
      if length mvars' >= optThreads opts
        then do
          threadDelay $ 5 * 1000 * 1000
          builder'' job mvars'
        else do
          mvar' <- newEmptyMVar
          _ <- forkIO $ buildNext opts job mvar'
          return $ mvar' : mvars'

    cleanMVars :: [MVar ()] -> IO [MVar ()]
    cleanMVars mvars = do
      mMVars <- forM mvars $ \mvar -> do
        mres <- tryTakeMVar mvar
        return $ maybe (Just mvar) (const Nothing) mres
      return $ catMaybes mMVars