module Development.Shake.Internal.Core.Pool(
    Pool, runPool,
    addPoolException, addPoolResume, addPoolStart, addPoolBatch,
    increasePool
    ) where
import Control.Concurrent.Extra
import System.Time.Extra
import Control.Exception
import Control.Monad.Extra
import General.Timing
import General.Extra
import qualified General.Bag as Bag
import qualified Data.HashSet as Set
data Queue a = Queue
    {queueException :: Bag.Bag a
    ,queueResume :: Bag.Bag a
    ,queueStart :: Bag.Bag a
    ,queueBatch :: Bag.Bag a
    }
lensException = (queueException, \x v -> x{queueException=v})
lensResume = (queueResume, \x v -> x{queueResume=v})
lensStart = (queueStart, \x v -> x{queueStart=v})
lensBatch = (queueBatch, \x v -> x{queueBatch=v})
lenses = [lensException, lensResume, lensStart, lensBatch]
newQueue :: Bool -> Queue a
newQueue deterministic = Queue b b b b
    where b = if deterministic then Bag.emptyPure else Bag.emptyRandom
dequeue :: Queue a -> Bag.Randomly (Maybe (a, Queue a))
dequeue q = firstJustM f lenses
    where
        f (sel, upd)
            | Just x <- Bag.remove $ sel q
            = do (x,b) <- x; return $ Just (x, upd q b)
        f _ = return Nothing
data Pool = Pool
    !(Var (Maybe S)) 
    !(Barrier (Either SomeException S)) 
data S = S
    {threads :: !(Set.HashSet ThreadId) 
    ,threadsLimit :: {-# UNPACK #-} !Int 
    ,threadsMax :: {-# UNPACK #-} !Int 
    ,threadsSum :: {-# UNPACK #-} !Int 
    ,todo :: !(Queue (IO ())) 
    }
emptyS :: Int -> Bool -> S
emptyS n deterministic = S Set.empty n 0 0 $ newQueue deterministic
worker :: Pool -> IO ()
worker pool@(Pool var done) = do
    let onVar act = modifyVar var $ maybe (return (Nothing, return ())) act
    join $ onVar $ \s -> do
        res <- dequeue $ todo s
        case res of
            Nothing -> return (Just s, return ())
            Just (now, todo2) -> return (Just s{todo = todo2}, now >> worker pool)
step :: Pool -> (S -> Bag.Randomly S) -> IO ()
step pool@(Pool var done) op = do
    let onVar act = modifyVar_ var $ maybe (return Nothing) act
    onVar $ \s -> do
        s <- op s
        res <- dequeue $ todo s
        case res of
            Just (now, todo2) | Set.size (threads s) < threadsLimit s -> do
                
                t <- forkFinallyUnmasked (now >> worker pool) $ \res -> case res of
                    Left e -> onVar $ \s -> do
                        t <- myThreadId
                        mapM_ killThread $ Set.toList $ Set.delete t $ threads s
                        signalBarrier done $ Left e
                        return Nothing
                    Right _ -> do
                        t <- myThreadId
                        step pool $ \s -> return s{threads = Set.delete t $ threads s}
                let threads2 = Set.insert t $ threads s
                return $ Just s{todo = todo2, threads = threads2
                               ,threadsSum = threadsSum s + 1, threadsMax = threadsMax s `max` Set.size threads2}
            Nothing | Set.null $ threads s -> do
                signalBarrier done $ Right s
                return Nothing
            _ -> return $ Just s
addPool (sel, upd) pool act = step pool $ \s ->
    return s{todo = upd (todo s) $ Bag.insert (void act) $ sel $ todo s}
addPoolException, addPoolResume, addPoolStart :: Pool -> IO a -> IO ()
addPoolException = addPool lensException
addPoolResume = addPool lensResume
addPoolStart = addPool lensStart
addPoolBatch = addPool lensBatch
increasePool :: Pool -> IO (IO ())
increasePool pool = do
    step pool $ \s -> return s{threadsLimit = threadsLimit s + 1}
    return $ step pool $ \s -> return s{threadsLimit = threadsLimit s - 1}
runPool :: Bool -> Int -> (Pool -> IO ()) -> IO () 
runPool deterministic n act = do
    s <- newVar $ Just $ emptyS n deterministic
    done <- newBarrier
    let cleanup = modifyVar_ s $ \s -> do
            
            case s of
                Just s -> mapM_ killThread $ Set.toList $ threads s
                Nothing -> return ()
            return Nothing
    let ghc10793 = do
            
            
            
            sleep 1 
                    
            res <- waitBarrierMaybe done
            case res of
                Just (Left e) -> throwIO e
                _ -> throwIO BlockedIndefinitelyOnMVar
    handle (\BlockedIndefinitelyOnMVar -> ghc10793) $ flip onException cleanup $ do
        let pool = Pool s done
        addPoolStart pool $ act pool
        res <- waitBarrier done
        case res of
            Left e -> throwIO e
            Right s -> addTiming $ "Pool finished (" ++ show (threadsSum s) ++ " threads, " ++ show (threadsMax s) ++ " max)"