-- Copyright: (c) 2013 GREE, Inc.
-- License: MIT-style

module System.Prefork.Settings (defaultSettings, relaunchSettings) where

import Control.Exception (SomeException, catch)
import System.Posix

import System.Prefork.Class
import System.Prefork.Types
import System.Prefork.Worker
import Control.Concurrent.STM
import qualified Data.Set as S
import qualified Data.Map as M
import Data.List
import Control.Monad

{- | default settings for defaultMain
     
     This just sends signals to child processes.
-}
defaultSettings :: PreforkSettings sc
defaultSettings = PreforkSettings {
    psOnTerminate      = \_config -> mapM_ (sendSignal sigTERM)
  , psOnInterrupt      = \_config -> mapM_ (sendSignal sigINT)
  , psOnQuit           = \_config -> return ()
  , psOnChildFinished  = \_config -> return ([])
  , psOnStart          = \_mConfig -> return ()
  , psOnFinish         = \_mConfig -> return ()
  , psUpdateServer     = \_config -> return ([])
  , psCleanupChild     = \_config _pid -> return ()
  , psUpdateConfig     = return (Nothing)
  }

{- | relaunch settings
     
     This requires 'PreforkResource' that describes resouces used by workers.
     'relaunchSettings' takes two functions.
     The one is 'update' and the other is 'fork'.
     'update' function is used for reading server configuration (usually from a file)
     and update 'sc' type.
     'fork' function is used for launching a new worker with a worker context.
     The worker context should be the instance of 'Eq' and 'Ord' classes because it 
     will be a element type of 'Set'.
-}
relaunchSettings :: (Ord w, Eq w)
                    => PreforkResource w
                    -> (PreforkResource w -> IO (Maybe sc))
                    -> (w -> IO (ProcessID))
                    -> PreforkSettings sc
relaunchSettings resource updateAction forkAction = defaultSettings {
      psUpdateConfig = updateAction resource
    , psUpdateServer = updateWorkers resource forkAction
    , psCleanupChild = cleanupChild resource
    , psOnChildFinished = relaunchWorkers resource forkAction
  }
  where
    -- Clean up application specific resources associated to a child process
    cleanupChild :: (Ord w, Eq w) => PreforkResource w -> sc -> ProcessID -> IO ()
    cleanupChild resource _config pid = atomically $ modifyTVar' (prProcs resource) $ M.delete pid
    
    -- Update the entire state of a server
    updateWorkers :: (Ord w, Eq w) => PreforkResource w -> (w -> IO (ProcessID)) -> sc -> IO ([ProcessID])
    updateWorkers resource forkAction _config = do
      workers <- readTVarIO (prWorkers resource)
      newPids <- forM (S.toList workers) $ \w -> do
        pid <- forkAction w
        return (pid, w)
      oldPids <- atomically $ swapTVar (prProcs resource) (M.fromList newPids)
      forM_ (M.keys oldPids) $ sendSignal sigTERM
      return (map fst newPids)
    
    -- Relaunch workers after some of them terminate
    relaunchWorkers :: (Ord w, Eq w) => PreforkResource w -> (w -> IO (ProcessID)) -> sc -> IO ([ProcessID])
    relaunchWorkers resource@PreforkResource { prProcs = procs, prWorkers = workers } forkAction _config = do
      (live, workers) <- atomically $ do
        live <- readTVar procs
        workers <- readTVar workers
        return (live, workers)
      newPids <- fmap M.fromList $ forM (S.toList workers \\ M.elems live) $ \w -> do
        pid <- forkAction w
        return (pid, w)
      atomically $ modifyTVar' procs $ M.union newPids
      return (M.keys newPids)

-- private
sendSignal :: Signal -> ProcessID -> IO ()
sendSignal sig cid = signalProcess sig cid `catch` ignore
  where
    ignore :: SomeException -> IO ()
    ignore _ = return ()